Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2009 (Lansdowne, VA) with:

Ask Ben: Extending Application.cfc And OnRequestStart() With SUPER

By Ben Nadel on

Got one for ya... I'm extending application.cfc with ye ole' proxy.cfc trick. In root/Application.cfc I cfinclude a config.cfm file that has some sitewide variables in the request scope like upload path, dsn, root URL, etc... I do this in onRequestStart(). In my root/sub/Application.cfc that extends the root cfc, how can I get access to the request scope that the root cfc set up??? I must be doing something wrong...

again:
root/app.cfc sets request.ds in onrequeststart()
root/sub/app.cfc extends root/app.cfc
root/sub/index.cfm does not have request scope vars from /root/app.cfc available

I'm confused. HELP! :)

I have one word for you: SUPER. The SUPER scope is created when one ColdFusion component extends another one. It is available to the extending component and gives access from the extending component up into the extended component, sometimes referred to as the "base component" or "super component." The SUPER scope not only allows us to get access to values in the super component (which isn't necessary since those values are also available in the extending component), but more importantly, it allows us to execute functions in the super component even when those functions are being overridden by the extending ColdFusion component (as they will be in our demo).

To leverage this and pull in the configuration data that you have in your root Application.cfc ColdFusion component, we are going to use the SUPER scope to invoke the OnRequestStart() event method of the root Application.cfc from the sub Application.cfc. But first, let's take a quick look at the root Application.cfc to make sure we are all on the same page:

  • <cfcomponent
  • output="false"
  • hint="Primary application event handler.">
  •  
  • <!--- Set application settings. --->
  • <cfset THIS.Name = "ExtendedAppDemo" />
  • <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
  •  
  • <!--- Set page request settings. --->
  • <cfsetting
  • requesttimeout="10"
  • />
  •  
  •  
  • <cffunction
  • name="OnRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Hanldes pre-page processing for each request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Page"
  • type="string"
  • required="true"
  • hint="The template requested by the user."
  • />
  •  
  • <!--- Read in / process configuration file. --->
  • <cfinclude template="config.cfm" />
  •  
  • <!--- Return out. --->
  • <cfreturn true />
  • </cffunction>
  •  
  • </cfcomponent>

Not much going on here. The Application is defined and then the OnRequestStart() method just includes the configuration file and returns out (it returns true which means that rest of the page request can be processed - more on this in a bit).

The configuration file that we are reading in just sets some values that we can use for testing:

  • <!--- Set up data source. --->
  • <cfset REQUEST.DSN = {
  • Source = "ExtendsAppDemo",
  • Username = "JazzyJazmin",
  • Password = "coolnhot"
  • } />
  •  
  • <!--- Get the root directory of the application. --->
  • <cfset REQUEST.RootDirectory = GetDirectoryFromPath(
  • GetCurrentTemplatePath()
  • ) />

Now, in a sub folder of the application, we are going to create another Application.cfc that extends the root one. In the following code, my extends path is really long since I don't have any mappings setup for this demo. I am going to shorten it for the output:

  • <cfcomponent
  • output="false"
  • extends="personal.ben......app_extend2.Application"
  • hint="Secondary application event handler.">
  •  
  • <cffunction
  • name="OnRequestStart"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="Hanldes pre-page processing for each request.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Page"
  • type="string"
  • required="true"
  • hint="The template requested by the user."
  • />
  •  
  •  
  • <!---
  • Since this application is extending the root
  • Application.cfc, let's leverage the pre-processing
  • power of the root OnRequestStart(). Check to see
  • what value (true/false) that the root application
  • would have returned for this request.
  •  
  • By calling SUPER.OnRequestStart( Page ), we are
  • giving the root application a chance to run logic
  • on the page request. **Remember that the
  • OnRequestStart() method can return false to kill the
  • current page request. Therefore, we have to check to
  • see what value would be returned and honor that.
  • --->
  • <cfif SUPER.OnRequestStart( ARGUMENTS.Page )>
  •  
  • <!--- Store the sub root directory folder. --->
  • <cfset REQUEST.SubDirectory = GetDirectoryFromPath(
  • GetCurrentTemplatePath()
  • ) />
  •  
  • <!--- Return out. --->
  • <cfreturn true />
  •  
  • <cfelse>
  •  
  • <!---
  • The root application returned false for this
  • page request. Therefore, we want to return
  • false to honor that logic.
  • --->
  • <cfreturn false />
  •  
  • </cfif>
  • </cffunction>
  •  
  • </cfcomponent>

In the sub Application.cfc, we don't have to define the application settings or the page request settings as those will all be inherited from the root Application.cfc. The only thing we have defined in this component is the OnRequestStart() method. Now, as you have experienced, once you have defined the OnRequestStart() method, the one in the base component will not be called; that is because you have overridden in. This is where our SUPER scope comes into play. Take a look at the first line of our OnRequestStart() method:

  • <cfif SUPER.OnRequestStart( ARGUMENTS.Page )>

We're doing two very important things here. For starters, by calling SUPER.OnRequestStart(), we are invoking the OnRequestStart() event method of the root Application.cfc. This will read in your configuration file and prepare the REQUEST scope. Then, the rest of the sub Application.cfc OnRequestStart() event method will execute and set up sub-app specific data. And, to be honest, we could have simply left it at that and your problem would have been solved.

However, we don't want to just invoke the parent application's OnRequestStart() event method and then forget about it; that would be to ignore the work flow architecture of ColdFusion application event handling. If you look at the OnRequestStart() event method, you will see that it returns a Boolean value. This return value has the power to halt the execution of the page request; if it returns false, ColdFusion will not process the rest of the page. Of course, since we are invoking the root method programmatically (and not via the internal ColdFusion process), its return value is not inherently appreciated. Therefore, we have to take it upon ourselves to check the return value of the root OnRequestStart() event method and, if it returns false, we have to then turn around and return false in the sub application's OnRequestStart() event method. Not only does this follow the standard ColdFusion event handling model, it also allows us to move a lot of potential security and processing logic up into the root application.

To see if this works, let's create an index.cfm page in the sub directory that will output values from both Application.cfc event methods:

  • <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  • <html>
  • <head>
  • <title>Sub Index File</title>
  • </head>
  • <body>
  • <cfoutput>
  •  
  • <h1>
  • Sub Index File
  • </h1>
  •  
  • <h4>
  • Application Name
  • </h4>
  •  
  • <p>
  • #APPLICATION.ApplicationName#
  • </p>
  •  
  • <h4>
  • DSN
  • </h4>
  •  
  • <p>
  • Source: #REQUEST.DSN.Source#<br />
  • Username: #REQUEST.DSN.Username#<br />
  • Password: #REQUEST.DSN.Password#<br />
  • </p>
  •  
  • <h4>
  • Root Directory
  • </h4>
  •  
  • <p>
  • #REQUEST.RootDirectory#
  • </p>
  •  
  • <h4>
  • Sub Directory
  • </h4>
  •  
  • <p>
  • #REQUEST.SubDirectory#
  • </p>
  •  
  • </cfoutput>
  • </body>
  • </html>

Notice that we are expecting the configuration information from the root Application.cfc and its config.cfm file as well as the SubDirectory value as defined by the sub Application.cfc. Running the above code, we get the following output (I have truncated some of the really long paths):

Sub Index File

Application Name
ExtendedAppDemo

DSN
Source: ExtendsAppDemo
Username: JazzyJazmin
Password: coolnhot

Root Directory
C:\Inetpub\wwwroot\.............\app_extend2\

Sub Directory
C:\Inetpub\wwwroot\.............\app_extend2\sub\

There you go. As you can see, our sub application page request gets the configuration file data from the root as well as path to our sub directory.

Hope that helps.




Reader Comments

I like. I too was wondering something similar, and failed to recognize that inheritance could apply here. Cool!

-Brian

Reply to this Comment

You're Too Fast, must need more 'real' work to do :)

This helped a lot. It's one of those things I have read about for the past few years I think since cfc's became available. It not until you actually need something that you realize just because you read it doesn't mean you learned anything.... I use CFC's very often, but rarely with inheritance. All of the proxy.cfc examples left this part out. thank you!

Oh, and yes.... SUPER is explained well in the adobe docs, but it's easy to forget that Application.cfc is still just a cfc with methods, so duh, the SUPER scope will work here...

Thank you very much!

Reply to this Comment

@Qasim Rasheed

Exactly, that's known as the application proxy method, which is what I use.

The problem arises when trying to make the variables created by the root cfc available to the sub/cfc. The use of the SUPER scope is designed specifically for that during inheritance situations.

Reply to this Comment

I actually asked ben about this same issue and got nearly the same response about 3 weeks ago.

We sent a few emails back and forth and I decided to keep just one application.cfc and use the cgi.script_name to determine what was being accessed. Then I made a bunch of functions that could be called from onRequestStart.

Super worked just like Ben explained, but a word of caution was issued about getting the application messy with multiple application.cfcs

Reply to this Comment

@Justin,

Good point. To this day, I have never actually used more than one Application.cfc. I like the idea of all the requests funneling through one App file, especially if they are all gonna extend that one anyway.

Reply to this Comment

Continuing the discussion on extending application.cfc, I've created an application.cfc in my 'com' folder and use that as the skeleton for the site. I then put an application.cfc in the root folder that extends com.application.

Since I use a basic code structure for every app I develop, this allows me to copy my base code to a new folder, make a few changes to the root application.cfc (passing vars to the com.application init function, for example, that sets up the normal cfapplication type settings) and I'm ready to go.

In the base application cfc (com.application), I have functions that are basic to any app I'd create and should never have to touch. In the root application cfc, I add any I'll need for the new app.

Just thought I'd put in my two cents :).

Reply to this Comment

@Ed,

Sounds pretty neat. I have always stayed away from that idea out of fear - fear that I would do something that would suddenly need me to change a whole bunch of other apps. I am not sure what that something would be, but I am afraid to find out.

But, it sounds like you have a good system going there. Good to hear it.

Reply to this Comment

So is this:
<cffunction name="onRequestStart">
<cfargument name="targetPage" type="string" required=true>
<cfif super.onRequestStart(targetPage)>
<cfreturn true />
<cfelse>
<cfreturn false />
</cfif>
</cffunction>

exactly the same as:
<cffunction name="onRequestStart">
<cfargument name="targetPage" type="string" required=true>
<cfreturn super.onRequestStart(targetPage) />
</cffunction>

Reply to this Comment

Hmmmm what is the path? I get the same error that the syntax is wrong no matter what I put in the path.
Basically I have parent at
http://www.mydomain.com
Then a subfolder at
http://www.mydomain.com/subfolder
Each has an application.cfc
The root folder is D:\Hosting\mydomain
The sub is D:\Hosting\mydomain\subfolder
What path would I use to extend application.cfc in the root folder?

Reply to this Comment

@Don,

That's an interesting question! I wonder, can you use application-specific mapping in the Application.cfc tag itself? I am guessing NO, but I don't know.

What you might have to do is create a CFAdmin mapping for the root of your app so that in the sub-folder, you can put:

extends="myapp.Application"

... to target the correct Application.cfc.

Reply to this Comment

@Don,

I just ran a quick test and it seems that the Application.cfc *cannot* define the mapping that it uses in its own Extends attribute.

... hmm, wait, one more test.

Reply to this Comment

@Ben,

OK, I have an idea, based sort of on what Sean Corfield wrote. Let me see if I can whip something up fast (then its time for the weekend, yo!!).

Reply to this Comment

@Ben,

Actually, no, didn't work :(

I guess, you need to do what Sean Corfield was saying in the link above.

Reply to this Comment

I'm thinking actually to do it fully it might be better to use 2 full application.cfc files in the root. Once called application.cfc and the other dupapplication.cfc like he is saying. But to truly extend the dup it really needs everything in it. Maybe not. I'll play with it some until the weekend hits Hawaii.
Yeh. That's why I can't think. Got the beach on my mind too much!

Reply to this Comment

Hey Ben,

I am using this method to extend my app.cfc in a logged in only section. I have this snippet just before the return true

<cfif not StructKeyExists(session.security,"loggedin") or (StructKeyExists(session,"security") and session.security.loggedin eq 0)>
<cflocation addtoken="no" url="/signin.cfm?err=notloggedin" />
</cfif>
<cfreturn true />

When my session times out while I am in the myaccount section, it will do the redirect, but I get an error email with the following...

An exception occurred when invoking a event handler method from Application.cfc The method name is: onRequestStart.

Says line 23 which is the cflocation line.

Any idea?

THanks,
Derek

Reply to this Comment

@Derek,

This is very strange. I get confused by these work/don't work scenarios. I don't even know how to go about fixing this. Maybe check to see if there are any patches available?

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.