404 error handling in ColdFusion on Wheels
Tuesday, March 30th, 2010This is fairly simple, but I figured that I would share my approach for 404 error pages in ColdFusion on Wheels and see if anyone has a different/better way of doing it. This example demonstrates code used on cfwheels.org.
The strategy
What I really wanted was a function that I could call whenever a given view’s record could not be found.
In my example case, I wanted to handle user IDs in the People Directory that represent records that don’t exist. I had ended up removing user 31 (and a few others), so I wanted to display a helpful 404 error message every time http://cfwheels.org/user/profile/31 was accessed.
404 error page
So the first step was to create the 404 page itself, which I stored at views/main/error404.cfm.
<cfset layout.title = "Page Not Found | ColdFusion on Wheels"> <cfset layout.header1 = "Page Not Found"> <cfset layout.breadcrumbs = ["Page Not Found"]> <!--- 404 error ---> <cfheader statuscode="404" statustext="Not Found"> <cfoutput> <p> We're sorry. We couldn't find the page that you're looking for. It has either been removed, or perhaps you are accessing an inaccurate <abbr title="Uniform Resource Locator">URL</abbr>. </p> <h2><label for="search-query-404">Search</label></h2> #startFormTag(controller="search", id="cse-search-box", method="get")# <div> <input type="hidden" name="cx" value="005724978648843866544:jpej79qhz14" /> <input type="hidden" name="cof" value="FORID:10" /> <input type="hidden" name="ie" value="UTF-8" /> <input id="search-query-404" type="text" name="q" /> <input type="submit" name="sa" value="Search" /> </div> #endFormTag()# <h2>Start from the Home Page</h2> <p><strong>#linkTo(text="ColdFusion on Wheels Home »", route="home")#</strong></p> </cfoutput>
The real meat is the fact that I put the <cfheader> 404 reference in the view file. I look at anything that’s sent to the browser as a job for the view to handle, so that’s why I put the call there instead of in the controller file. In fact, because the page is fairly “dumb,” I didn’t put anything in the Main controller.
Rendering helper
I also put a quick render404() function in the base controller at controllers/Controller.cfc so that I wouldn’t need to manually call renderPage(controller="main", action="error404") every time that I wanted to reference this new view. Your preference may be to not do this, but I’ll leave that up to you.
<cffunction name="render404" hint="Renders a 404 error page."> <cfset renderPage(controller="main", action="error404")> </cffunction>
Handling 404 errors in the controller
The last step involved actually using this functionality in the case that an invalid record ID was passed in the URL. So the user/profile action now looks like this:
<cffunction name="profile" hint="Displays user profile."> <cfset user = model("customer").findByKey(params.key)> <cfset loggedInUser = getLoggedInCustomer()> <!--- If profile found, show it ---> <cfif IsObject(user)> <cfset sites = user.sites(where="isApproved=1")> <cfset plugins = user.plugins(where="isApproved=1")> <!--- 404 error if not found ---> <cfelse> <cfset render404()> </cfif> </cffunction>
Fairly simple stuff. When loading the user object, I check to see if an object was returned. If not, then show the 404 page. Pretty reusable, and it only requires an additional if/else block in the controller to decide what to do.
Plus the file at events/onmissingtemplate.cfm can just use <cfhttp> to phone http://cfwheels.org/main/error404 in order to display the exact same error message during a more generic “template not found” scenario.
Besides identifying other places in the application to call render404(), that’s pretty much it.



April 12th, 2010 at 7:37 am
This seems like a good way to handle 404 errors. One problem I’ve experienced is Google still indexing my pages that no longer exist. I send those requests to a custom 404 error page that I’ve created, however I have not been using the CFHEADER tag to send a 404 error in the header. I don’t know this for fact but I’m thinking this is what’s causing Google to continue indexing these pages. I’m not sending them a “true” 404 error page.
Anyways, good post!
April 12th, 2010 at 9:10 am
Good to hear from you, Grant. (Haven’t heard from you in a while!)
That part involving <cfheader> is very important. Your “not found” page is just a regular web page to Googlebot if you don’t send the 404 header.
December 3rd, 2010 at 10:38 pm
Hi Chris, is there a way to create an onMissingAction method inside the controller that get’s called anytime there is a 404 call on that controller without applying conditional logic?
December 5th, 2010 at 10:42 pm
George, the only thing that Wheels can do automatically for you is to run the template in events/onmissingtemplate.cfm. That runs when the client tries to access a controller or action that doesn’t exist.
The scenario I describe in this post addresses the situation where a record in the DB wasn’t found (or some other similar logic). Right now, there isn’t really a way for Wheels to “know” that a record wasn’t found.
In a pre-1.0 release of Wheels, it used to throw a Wheels.RecordNotFound exception when a record wasn’t found. It was quite a different way to do things because instead of checking IsObject(user), you would put a try/catch block around the finder call. Writing a plugin to have the finders throw an exception when a record is not found and then have Wheels display a global template would be the best way to have an “automatic” way of handling what you request.