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 OnSessionStart() In A Sub Directory Application.cfc

By Ben Nadel on
Tags: ColdFusion

Ben, I have a root Application.cfm and perhaps 20 sublevel Application.cfms in a web portal that my client created. Each of the old sublevel Application.cfms had a cfinclude that picked up the root session variables from the root Application.cfm. This reduced session variables to only those really needed. I'm trying to improve the system and one of the steps is moving to Application.cfcs. I have done this using the extends attribute, but my sublevel Application.cfcs had additional session variables being set if a user invoked the particular sublevel applications. I put these in the onSessionStart method, but they are not getting picked up unless I rename the this.name of the Application.cfc, but then of course I lose the root session variables. Do I need to do something similar to what you have done with the use of Super or with the onRequestStart method?

To abstract out your problem, what you are trying to do is both utilize and override a base component method. The way to do this is actually quite simple - you have to manually invoke the given base method from within the extending, overriding method. The caveat behind this is knowing that the base method is available in the SUPER scope of the extending object.

To explore this, let's set up a really simple base Application.cfc:

  • <cfcomponent
  • output="false"
  • hint="I provide application settings and event handlers.">
  •  
  • <!--- Define application settings. --->
  • <cfset THIS.Name = "SubSessionTesting" />
  • <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 1, 0 ) />
  • <cfset THIS.SessionManagement = true />
  • <cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 5 ) />
  •  
  •  
  • <cffunction
  • name="OnSessionStart"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I fire when a session starts.">
  •  
  • <!--- Create default session values. --->
  • <cfset SESSION.PointOfEntry = {
  • Referer = CGI.http_referer,
  • DateCreated = Now()
  • } />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, this base Application.cfc defines the application settings and the core OnSessionStart() method. Within this base OnSessionStart() method, we are creating a PointOfEntry struct in our session. All of our sub-applications need this functionality so, when we extend this object, we want to be sure not to lose this method execution.

Now that we have that Application.cfc in place, let's create a sub directory with another Application.cfc that extends our base component:

  • <cfcomponent
  • extends="sub_app_session.Application"
  • output="false"
  • hint="I provide application settings and event handlers.">
  •  
  • <!--- Inherit based application settings. --->
  •  
  •  
  • <!--- Override the base OnSessionStart() event method. --->
  • <cffunction
  • name="OnSessionStart"
  • access="public"
  • returntype="void"
  • output="false"
  • hint="I fire when a session starts.">
  •  
  • <!---
  • Invoke the base session start method. This method is
  • located in the SUPER scope. This will get the core
  • session initialization functionality to execute.
  • --->
  • <cfset SUPER.OnSessionStart() />
  •  
  • <!---
  • Now that we have our base session information,
  • we can add session data that is specific to this
  • sub application.
  • --->
  • <cfset SESSION.User = {
  • ID = 0,
  • Name = "",
  • Email = ""
  • } />
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

In this sub-directory Application.cfc, we don't have to define any application-level settings because those are going to be inherited from the base Application.cfc. We do, however, want to augment but not fully override the OnSessionStart() method. As such, we do supply an OnSessionStart() event method in our extending CFC. But, to make sure that we get the benefits of the base Application.cfc, we are manually invoking the OnSessionStart() event method in our base component with this line of code:

  • <cfset SUPER.OnSessionStart() />

That's the magic line right there. This executes the OnSessionStart() in our base Application.cfc, creating that PointOfEntry struct. Then, after that fires, we add the additional data required by the sub application - the User struct.

To make sure that this is all wired up properly, I created an index.cfm page in our sub directory that simply CFDumps out the SESSION scope. When we run that code, we get this output:

 
 
 
 
 
 
SESSION Variables Defined By Both The Base OnSessionStart() Method As Well As By The Extending OnSessionStart() Method. 
 
 
 

As you can see, the SESSION scope gets the entries created by both the base Application.cfc as well as the extending one. Had we not used the SUPER-scope-based method, we would have only gotten the User struct.

I hope this helps in some way.




Reader Comments

Good info Ben, too bad super is not really documented in CF afaik.
I have used that method for session/application methods before but one thing that I think that I may have hit into before is:

User goes to root, gets point of entry created. Then he navigates into the sub directory, at which point his session already exists thus the sessionStart would not fire and he would be devoid of the user object.

That seemed to be the only cavet to watch out for. I had this sort of issue where there was a public part of an app at the root then a client or user folder deeper down that only there required a user object, but placing it at this level then would mean it wasn't created if they had view the public section first - which would almost always happen.

I think I may have gotten around it by ensuring that a 'started' flag was set in the request start of the extended application.cfc, if not running the session start method. Once the session start method ran set the started flag so it would not run each request.

Reply to this Comment

@shuns,

That's an interesting situation; but, I am not sure that it is one that is really a proper use-case. If you wanted the user to land in the root directory and then navigate to a sub-directory, I can't think of a reason why you'd want their session data to change. What I envisioned here was a set of applications, maybe at sub-domains, where you could never actually hit the root Application.cfc directly (all session instances would be extensions of the root).

However, you could have the sub-directory Application.cfc override the THIS.Name attribute of the Component and have each sub-directory in its own actual application name space. Then, they would each get their on OnSessionStart() event firing upon access.

Reply to this Comment

Ben,

I think what shuns is getting at is what I experienced as well. Here's my solution.

I started with your approach and it worked in part. However, when I entered the app from the homepage (fine), then to one of the sublevel pages (fine) and then hit a second sublevel page in the same directory where I needed the same sublevel Application.cfc session variables(error). A session variable from that sublevel Application.cfc is no longer set and throws an error. Why were my sublevel session variables wiped out? I don't know, however this check in the onRequestStart worked for me ensuring that on every request my main and sublevel session variables are set. I just don't get why it is needed.

<cffunction name="onRequestStart" access="public" returnType="boolean" output="false"
hint="Run before each page request is processed">
<cfargument name="strPage" type="string" required="true"
hint="the template requested by the user" />

<!---Check for session variables from the root Application.cfc --->
<cfif not IsDefined("session.aMainAppVar")>
<!---Instantiate the main Application.cfc session variables--->
<cfset super.onSessionStart() />
</cfif>

<!---Check for session variables from the local Application.cfc --->
<cfif not IsDefined("session.aSubLevelAppVar")>
<!---Instantiate the local Application.cfc session variables--->
<cfset onSessionStart() />
</cfif>

<cfreturn true />
</cffunction>

Reply to this Comment

dickbob,
I chose to create a path in my CF Administrator to my root area and use it for the extends, so it looks like:
extends="MyAppRoot.Application"

It seems cleaner and clearer. I didn't like the thought of an extra file whose purpose was for this and the other suggested tip using an undocumented pathing syntax did not work for me for some reason.

Reply to this Comment

@BrianO, of course your method is fine and works perfectly in fact I used to the same. Just a suggestion to play the OO way and reduce the number of dependencies outside of your code :-)

Reply to this Comment

@dickbob,
Sure. Also I've read you can now define your mappings in your code in Application.cfc (This.mappings). We're getting ready to move into an environment where I have no direct control over my Administrator settings and will probably use that.

Reply to this Comment

@BrianO,

I am not sure if the Application.cfc-mappings pertain to CFC "extends" attribute. I think that is mainly for includes and custom tags.

Reply to this Comment

@Ben Nadel,
Unfortunately you are correct. I just tested it and although it works with the Administrator Server Settings > Mappings page, it fails with the mappings structure in Application.cfc. However all my other mappings do work fine using that structure as follows in the Application.cfc:

<cfset this.mappings["/MyAppComponents"]="D:\Web\MyApp\AnotherDir\Components" />

The description in the Administrator states: "... ColdFusion also uses mappings to find ColdFusion components (CFCs). The cfinvoke and cfobject tags and CreateObject function look for CFCs in the mapped directories. "

Curious why my Logical Path /MyAppRoot maps fine to my root Directory Path D:\Web\MyApp in the CF Admin for use in the extends attribute, but not with the mappings structure in Application.cfc.

Reply to this Comment

@BrianO,

I guess it's a bit of a chicken-and-egg problem. It can't use the mappings until it actually instantiates the Application.cfc object. But, it can't instantiate the object until it resolves any Extends mapping. Grrr!

Reply to this Comment

@Ben Nadel,
Probably so. Interesting that my Application.cfcs use the component mapping I just mentioned in the this.mappings to do a database lookup via a cfc in the onSession methods for the main and sublevel Application.cfc, but the extends is earlier at least in the sublevel Application.cfc where it is used.

Perhaps it would work if used between 2 CFCs at a lower level, just not the main, but I need to get back to studying for my CF8 certification exam.

Reply to this Comment

@Brian
Yeah, good that they have that, but looks to me like those are CF8 docs and when I was first learning CFCs with CF7 I never saw anything about it.

@Ben / Brian
Yeah what I was meaning is say you have:

Application.cfc
index.cfm

onSessionStart() {
session.started = now();

// other code etc.
}

users/Application.cfc
users/index.cfm

onSessionStart() {
super.onSessionStart();

session.user = createObject("component", "User.cfc").init();

// other code etc.
}

If the user hits the root of the root index.cfm or other page the root Application.cfc fires the session start event. As far as CF is concerned the session start event has already happened, meaning that if they then now browse to the: users/index.cfm page, that Application.cfc file's onSessionStart would not run until it was required (i.e. they have no session).

This would mean that the session.user instantiation would not run, neither any of the other code. This is what I have experienced before and if you think about it makes sense - there is no problem with the super call no anything else, it's just that you are relying on CF to call the method rather than programatticaly calling it and it won't call it if the session exists.

So I ended up doing something like:

users/Application.cfc

onSessionStart() {
super.onSessionStart();

variables.initSession();
}

onRequestStart() {
super.onRequestStart();

variables.initSession();
}

initSession() {
if (not structKeyExists(session, "userSession")) {
session.userSession = true;
// add extra code here
}
}

Now that code is just very roughly typed in, but that same premise sorted that issue out for me, so that I could guarantee that I would get the extending benefit, but also have the extra code created regardless if the session was already created or not.

Reply to this Comment

It seems that the base Application.cfc MUST have an onSessionStart handler.I'm noticing that if I have an onSessionStart handler in a sub-directory Application.cfc, and don't have one in the base Application.cfc, the onSessionStart handler never gets called/run, thus the Session vars I'm trying to initiate aren't initiated.

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.