Monday, December 14, 2009

Handling HTTP Request Parameters in ColdFusion

One thing that always kind of annoyed me in ColdFusion is the separation of HTTP request parameters, meaning parameters for GET requests are put into the url scope and POST parameters are put into the form scope. While this makes sense technically, I find it kind of annoying. For the most part, I don't care where the parameters come from.

Most ColdFusion frameworks alleviate this problem by combining the scopes for you. For example, Model-Glue and Mach-II create an event object that exposes parameters via event.getValue(key) or event.getArg(key). FW/1 combines the variables into request.context, which is then aliased as rc inside your controllers and views. ColdBox does a little bit of both, by having event.getValue(key) inside your controllers and rc inside your views.

While this makes working with request parameters easier, it still feels a little cumbersome having to go through getters and setters or cryptically-named structs (rc? really?). I'd rather have all the parameters combined into a single scope from the get-go.

One of the many things I like about Ruby on Rails and Grails is the params scope, which contains all GET and POST request parameters. Not surprisingly, CFWheels also has the params scope.

However, what happens if you're not using CFWheels, or even a framework at all? I want the same functionality, but without the overhead of adding a framework. With a little inspiration from Ben Nadel, here's my solution:

Application.cfc

component {

function onRequestStart() {

var params = {};
structAppend(params,url);
structAppend(params,form);
getPageContext().getFusionContext().hiddenScope.params = params;

}

}


Now all url and form variables are combined into a single params struct at the beginning of each request, which is then made globally accessible throughout my application, just like a built-in scope. Short and simple, no framework necessary.

On a final note, while this does work, I don't know if all the ColdFusion "experts" would consider it a best practice to add a new "scope" to the language. But in my opinion, it's isolated and helps solve a problem, so why not?

9 comments:

  1. Yeah, I don't really care for using the hiddenScope thing, but that's another discussion.

    The main thing you should point out in your example is that if duplicate keys exist then the form scope entry (again, in your example) will overwrite the url scope variable. That can be a bit tricky to folks who use this method.

    ReplyDelete
  2. @Todd,

    Good point about duplicate keys. Most frameworks do mention that the form scope takes precedence over the url scope. I just didn't bother to point it out.

    Another thing to mention would be that the form scope doesn't exist in WSDL-base remote requests, so you'd probably want to throw an isDefined('form') in there too.

    ReplyDelete
  3. Hmm, very interesting. I had never thought of using it in that respect.

    ReplyDelete
  4. I don't feel comfortable using getPageContext().getFusionContext().hiddenScope.params ...

    Is this documented? or undocumented?

    ReplyDelete
  5. @Henry,

    According to Ben's post it's undocumented.

    ReplyDelete
  6. For those of us who have worked with ColdBox, the rc scope isn't "cryptically named". It stands for "request collection." In earlier versions, you had to use event.getValue(key) in both controllers and views (and you still can do it that way). But it became a convention to do rc = event.getCollection() which returns a structure with all your request collection variables so you didn't have to use getters. Eventually, that "rc scope" was automatically made available to views.

    ReplyDelete
  7. @blitz,

    I've worked with ColdBox before and I'm familiar with the request collection. I would just prefer something a little more straight-forward than "rc", like "params". People new to the ColdBox framework probably have no clue what "rc" is, where they could take a pretty good guess if it were named "params".

    ReplyDelete
  8. Is this code put inside a cfscript tag within the onRequestStart component? I am one of these people that need every little detail spelled out before I can wrap my brain around it.

    ReplyDelete
  9. In the example I'm using an Application.cfc written in script. The tag-based equivalent would be:

    <cfcomponent>

      <cffunction name="onRequestStart">

        <cfset var params = {} />
        <cfset structAppend(params, url) />
        <cfset structAppend(params, form) />
        <cfset getPageContext().getFusionContext().hiddenScope.params = params />

      </cffunction>


    </cfcomponent>

    ReplyDelete