Skip to main content
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Luke Brookhart
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: Luke Brookhart

Ask Ben: Accessing Cached CFCs With AJAX via Remote Proxies

By
Published in , , Comments (27)

Hi Ben, not sure if you already have a post on this, as I'm kind of at a loss with what to search for. I'm building a small application that makes use of AJAX calls (using jQuery) to a CFC instance, saved in my application scope. Making normal calls to this will persist alreay-added data, like you would expect. However, when I make my AJAX call, it seems to create a new instance each time, instead of using my cached version. Is there something I'm missing here?

It would be great if you could point me in the right direction. Many Thanks.

When you directly access a ColdFusion component on the server via AJAX, ColdFusion creates a new instance of the component and then executes the requested method. That is why, as you are finding out, none of the cached data is available in the AJAX request. The data that you are after is being stored inside an instance of the component that is, itself, being cached within the application. Because there is no way to directly access the ColdFusion memory space via AJAX, what we have to do is create some sort of ColdFusion-based intermediary that will act as a conduit between our AJAX call and the cached ColdFusion components.

To help visualize this kind of architecture, I have put together the following diagram:

AJAX Remote Proxy Application Architecture.

As you can see, we are going to create a "remote proxy" object. Our AJAX request will post to this remote proxy which will, in turn, access the ColdFusion application. And, since the remote proxy is ColdFusion-based (a CFC or CFM file), it has no problem reaching into the ColdFusion memory space and executing cached instance methods.

To demonstrate this concept, I have put together a small demo application that manages a list of messages. The ColdFusion application will create and cache an instance of a MessageService.cfc component that contains a collection of messages. We will then have one client form that can add to this list of messages using AJAX calls. With every message that is added, the entire list of messages is pulled down and displayed:

Lets take a quick look at the Application.cfc. There's not much going on here - all we are doing is creating an instance of MessageService.cfc and caching it in the APPLICATION scope during application initialization:

Application.cfc

<cfcomponent
	output="false"
	hint="I provide application settings and define event handlers.">

	<!--- Define application settings. --->
	<cfset THIS.Name = "Ajax #Hash( GetCurrentTemplatePath() )#" />
	<cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
	<cfset THIS.SessionManagement = false />


	<cffunction
		name="OnApplicationStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I execute when an application needs to be initialized.">

		<!--- Clear any existing data (if manually init). --->
		<cfset StructClear( APPLICATION ) />

		<!--- Create and cache a message service. --->
		<cfset APPLICATION.MessageService = CreateObject( "component", "MessageService" ).Init() />

		<!--- Return true to let app fully initialize. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="OnRequestStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I execute when a request needs to be initialized.">

		<!--- Check for manual initialization. --->
		<cfif StructKeyExists( URL, "reset" )>

			<!--- Manually re-initialize application. --->
			<cfset THIS.OnApplicationStart() />

		</cfif>

		<!--- Return true to let request fully initialize. --->
		<cfreturn true />
	</cffunction>

</cfcomponent>

Next, let's look at the MessageService.cfc. This ColdFusion component simply maintains an array of messages that can be added to and cleared.

MessageService.cfc

<cfcomponent
	output="false"
	hint="I provide methods for managing messages.">


	<cffunction
		name="Init"
		access="public"
		returntype="any"
		output="false"
		hint="I return an intialized object.">

		<!--- Create instance variables. --->
		<cfset VARIABLES.Instance = {
			Messages = []
			} />

		<!--- Return this object. --->
		<cfreturn THIS />
	</cffunction>


	<cffunction
		name="AddMessage"
		access="public"
		returntype="any"
		output="false"
		hint="I add a message to the message collection.">

		<!--- Define arguments. --->
		<cfargument
			name="Message"
			type="string"
			required="true"
			hint="I am the message being added."
			/>

		<!--- Add the message to the collection. --->
		<cfset ArrayAppend(
			VARIABLES.Instance.Messages,
			ARGUMENTS.Message
			) />

		<!--- Return this object for method chaining. --->
		<cfreturn THIS />
	</cffunction>


	<cffunction
		name="ClearMessages"
		access="public"
		returntype="any"
		output="false"
		hint="I clear the message collection.">

		<!--- Reset the message collection variable. --->
		<cfset VARIABLES.Instance.Messages = [] />

		<!--- Return this object for method chaining. --->
		<cfreturn THIS />
	</cffunction>


	<cffunction
		name="GetMessages"
		access="public"
		returntype="array"
		output="false"
		hint="I return the message collection.">

		<cfreturn VARIABLES.Instance.Messages />
	</cffunction>

</cfcomponent>

So far, not much going on. You'll notice that in the above component, none of the methods are given remote access. This is because we don't actually want to access them directly via AJAX or you will discover (as you already have) that a new instance is created and none of the cached data is available.

That brings us to the remote proxy. When I make AJAX calls, I like all of my AJAX calls to return a unified response. No matter what kind of request I am making, even if it is a request that is not intended to return data, I like ColdFusion to return an API response with the following structure:

  • Success - Boolean to flag success of failure of the request.
  • Errors - Array of Property/Error structures.
  • Data - Any data that needs to be returned as part of a successful request.

This way, the client is always aware of the success / failure of a given request.

Because I like using this unified response, I create a base proxy component that will take care of creating and updating this API response:

RemoteProxy.cfc

<cfcomponent
	output="false"
	hint="I provide remote proxy functionality for AJAX calls that need to access the application.">


	<cffunction
		name="NewResponse"
		access="public"
		returntype="struct"
		output="false"
		hint="I create a new instance of the unified response object used in the AJAX requests.">

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Create a response object. --->
		<cfset LOCAL.Response = {
			Success = true,
			Errors = [],
			Data = ""
			} />

		<!--- Return new response object. --->
		<cfreturn LOCAL.Response />
	</cffunction>


	<cffunction
		name="AddResponseError"
		access="public"
		returntype="struct"
		output="false"
		hint="I add the given error to the given response object.">

		<!--- Define arguments. --->
		<cfargument
			name="Response"
			type="struct"
			required="true"
			hint="I am the response object to which an error is being added."
			/>

		<cfargument
			name="Property"
			type="string"
			required="true"
			hint="I am the property in error."
			/>

		<cfargument
			name="Error"
			type="string"
			required="true"
			hint="I am the error message."
			/>

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Create the error struct. --->
		<cfset LOCAL.Error = {
			Property = ARGUMENTS.Property,
			Error = ARGUMENTS.Error
			} />

		<!--- Add error to the response. --->
		<cfset ArrayAppend(
			ARGUMENTS.Response.Errors,
			LOCAL.Error
			) />

		<!--- Flag the response as non-successful. --->
		<cfset ARGUMENTS.Response.Success = false />

		<!--- Return the updated response. --->
		<cfreturn ARGUMENTS.Response />
	</cffunction>

</cfcomponent>

Now that we have that class defined, I will create sub-classed remote proxies for specific purposes. In our case, I am going to create a remote proxy specifically for managing messages. It is this ColdFusion component that we will be accessing directly via AJAX requests:

RemoteMessageService.cfc

<cfcomponent
	extends="RemoteProxy"
	output="false"
	hint="I provide remote proxy functionality for the MessageService.">

	<!---
		NOTE: No need for an "Init" method as the following
		methods are going to be called on a one-off basis per
		AJAX request.
	--->

	<!---
		Because we are breaking encapsulation throughout this
		component, it's best to store a global reference to it
		here in case it needs to change (will only need to be
		changed here).
	--->
	<cfset THIS.MessageService = APPLICATION.MessageService />


	<!--- NOTE: Give all methods REMOTE access. --->


	<cffunction
		name="AddMessage"
		access="remote"
		returntype="struct"
		returnformat="json"
		output="false"
		hint="I add a message to the cached message collection.">

		<!--- Define arguments. --->
		<cfargument
			name="Message"
			type="string"
			required="true"
			hint="I am the message being added."
			/>

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Create a new response. --->
		<cfset LOCAL.Response = THIS.NewResponse() />

		<!--- Check to see if the message is valid. --->
		<cfif Len( Trim( ARGUMENTS.Message ) )>

			<!--- Add the message. --->
			<cfset THIS.MessageService.AddMessage(
				Trim( ARGUMENTS.Message )
				) />

		<cfelse>

			<!--- Add the error. --->
			<cfset THIS.AddResponseError(
				LOCAL.Response,
				"Message",
				"Message must not be empty."
				) />

		</cfif>

		<!--- Return the response. --->
		<cfreturn LOCAL.Response />
	</cffunction>


	<cffunction
		name="GetMessages"
		access="remote"
		returntype="struct"
		returnformat="json"
		output="false"
		hint="I return the cached message collection.">

		<!--- Define the local scope. --->
		<cfset var LOCAL = {} />

		<!--- Create a new response. --->
		<cfset LOCAL.Response = THIS.NewResponse() />

		<!--- Set the messages as the response data. --->
		<cfset LOCAL.Response.Data = THIS.MessageService.GetMessages() />

		<!--- Return the response. --->
		<cfreturn LOCAL.Response />
	</cffunction>

</cfcomponent>

Because we are accessing this ColdFusion component directly via AJAX, a new instance of it will be created every time. As such, we don't have a chance to gracefully initialize this object, injecting our dependencies (ie. the MessageService instance). To get around this, we have to bend the rules of encapsulation a bit and put direct references to the MessageService.cfc instance that is cached within the APPLICATION scope. While this is a necessary "evil," I like to curb the fallout of this by defining object aliases at the top of the remote proxy sub-class for the rest of the methods to use. In doing so, I make sure that if the scope-breaking-reference every needs to be changed, at least it only needs to be changed in one place (at the top).

That said, you will notice that each method in this remote proxy sub-class has "remote" access set. Each method also returns an instance of the unified API response. If you read over the contents of the functions, you will notice that the functions are doing little more than calling the appropriate methods on the cached MessageService.cfc instance. By using this man-in-the-middle design (anti)pattern, even though the remote proxy is re-created for each AJAX call, we can still take full advantage of ColdFusion's caching mechanisms.

To tie this all together, I create a simple index page that will perform two jQuery-powered AJAX calls - one to post a new message and one to get the list of updated messages to be displayed on the page.

Index.cfm

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>Accessing Cached CFCs With AJAX</title>
	<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
	<script type="text/javascript">

		// Initialize the page.
		function Init(){
			var jForm = $( "form:first" );

			// Hook up form submission to perform AJAX.
			jForm.submit(
				function(){
					// Add message.
					AddMessage( jForm );

					// Cancel default form submission.
					return( false );
				}
				);
		}


		// I add the message via AJAX.
		function AddMessage( jForm ){
			$.ajax(
				{
					method: "post",
					url: "RemoteMessageService.cfc",
					data: {
						method: "AddMessage",
						message: jForm.find( ":text" ).val()
						},
					dataType: "json",

					// AJAX response handler.
					success: function( objResponse ){
						// Check to see if the response was successful.
						if (objResponse.SUCCESS){

							// Clear form field.
							jForm.find( ":text" ).val( "" );

							// Update message list.
							GetMessages();

						} else {

							// Alert errors.
							AlertErrors( objResponse.ERRORS );

						}
					}
				}
				);
		}


		// I get the messages via AJAX and populate the OL.
		function GetMessages(){
			$.ajax(
				{
					method: "post",
					url: "RemoteMessageService.cfc",
					data: {
						method: "GetMessages"
						},
					dataType: "json",

					// AJAX response handler.
					success: function( objResponse ){
						var jList = $( "#messages" );

						// Check to see if the response was successful.
						if (objResponse.SUCCESS){

							// Clear the messages.
							jList.empty();

							// For each message, add to the list.
							$.each(
								objResponse.DATA,
								function( intI, strMessage ){
									jList.append(
										"<li>" +
										strMessage +
										"</li>"
										);
								}
								);

						} else {

							// Alert errors.
							AlertErrors( objResponse.ERRORS );

						}
					}
				}
				);
		}


		// I alert the API response errors.
		function AlertErrors( arrErrors ){
			var strErrorMessage = "Please review the following:\n\n";

			// Loop over each message to build the error.
			$.each(
				arrErrors,
				function( intI, objError ){
					strErrorMessage += ( objError.ERROR + "\n" );
				}
				);

			// Alert the error message.
			alert( strErrorMessage );
		}


		// When page is ready, initialize it.
		$( Init );

	</script>
</head>
<body>

	<h1>
		Accessing Cached CFCs With AJAX
	</h1>

	<form>

		<p>
			<label>
				New Message:
			</label>
			<br />

			<input type="text" name="message" size="50" />
			<input type="submit" value="Add Message" />
		</p>

	</form>


	<h2>
		Existing Messages
	</h2>

	<ol id="messages" />

</body>
</html>

I hope that helps you build your AJAX applications.

Want to use code from this post? Check out the license.

Reader Comments

5 Comments

Hey Ben -

Thanks for the thorough demonstration. Not sure how you find time to write a book everyday, but this is much appreciated. I am in the midst of learning jQuery. Can you point me to any good jQuery resources?

16 Comments

Ben, this is a brilliant dissertation. I read it carefully and am running your example. I've learned not only about the technique of using a cfc remote proxy but also about better software design in your approach. Thanks so much!

19 Comments

A really great post Ben (loving the diagram :) Really appreciate the time you spent on this. I would have loved to get into deep conversation over this but you've outright covered everything :)

14 Comments

Sweet. Creating the alias in the pseudo-constructor of the remote proxy is a great idea. Like you said, it makes the "evil" of referring to the application scope cleaner and more maintainable. Kudos.

34 Comments

How was the author of the question able to notice that new instances of the component were being created each time a call was made? Or was it obvious because he didn't have access to the application scope from AJAX?

19 Comments

@paolo

Similar to Bens demo, i was storing the data in the instance, and when x amount were added it would return true. Of course, this was never fulfilled

12 Comments

Ben,

This post touches on perhaps the core of why I'm not feeling completely comfortable working with AJAX requests until now - they lay outside the context of the web application they work with where persistent CF scopes often form the backbone of that context, especially when using frameworks. I've been experimenting with simply running AJAX requests through the framework. You gain access to all your persistent scopes in this way, another method of essentially accomplishing a similar thing as you've outlined here. This works well for AJAX posts, which can be persisted to the DB with the existing framework ORM instance and structure without any trouble at all (with the added benefit that if you're behind a login, you don't open a potential security hole - your post still runs through your security check).

It's a little awkward for returning JSON tho' (in a ModelGlue app at least). Rather than picking up the string directly from the object method, it needs to be run through a display template before the AJAX call would have access to the string needed. Sometimes it seems it would be convenient to access the singleton instances within a framework directly.

So I'm considering injecting the ModelGlue instance (which is in application scope) using your nifty method into an AjaxProxy.cfc, and then using getBean() (a method available on the MG instance to return any singletons managed by ColdSpring) as a sort of back door into these singletons. I still don't know if I like the idea, but your post has, I think, cleared up this issue for me well enough that I can find my way from here! So thanks very much for the post!

By the way, it occurred to me that the same proxy approach could be used for access to any session or server scoped instances or indeed variables of any kind.

15,841 Comments

@Nando,

Yeah, you can use this technique to access any kind of persisted values, or any new values created via the application. The remote proxy is basically just a web-service API for your application. Of course, since you are calling it from a browser, all the same session values are passed (CFID, CFTOKEN, etc.)... had you called these methods via WSDL (Soap-based) calls, then your sessions would not work as expected.

That said, while I have not tried ModelGlue, I have done things where the API request is funneled through my framework. For example, in my framework, all api-based actions start with "api". So, you might have a regular command:

do=contacts.get-list

.. you might have the api command:

do=api.contacts.get-details

The first renders a page, the second returns JSON.

To have the latter return JSON, it simply uses a different rendering template. Rather than using a template that returns HTML, I use one that takes the approropriate "content" variable and returns it as JSON via CFContent.

2 Comments

Ben,
What are the pros and cons of what you have suggested here compared with ajaxCFC at http://ajaxcfc.riaforge.org/ ?

It seems that ajaxCFC has not been worked on since 2007, so is what you have done here a replacement for ajaxCFC, or what?

Thanks,
Martyn

15,841 Comments

@Martyn,

I have not looked at AjaxCFC in a long while. I am not sure that it will have any pros or cons. When it comes down to it - we're just making simple AJAX calls. Any framework / toolset that you download is just going to provide some sort of structure surrounding that idea. Each will probably have benefits, depending on what you need it to do.

2 Comments

Hi Ben,

It seems that PHP is much faster than CF to run. Are CFCs slower than PHP classes? Is ColdFusion just a slower language than PHP on identical hardware? Could using PHP classes actually be a better option for increased performance rather than CFCs held in server memory?

42 Comments

I have found that it really depends what you are doing. If you are going for true speed, you shouldn't be using either of those. Use Java, or something low-level. The reason you use PHP or CF is because you get the job done quick and well.

That is not to say that CF cannot be fast, but when you move to higher-level languages (and then use frameworks, etc), you are going to take a performance hit. There is no mystery there.

But is a performance hit worth it if you are gaining a bunch of time in development? I think so.

2 Comments

I really like the article and I've implemented it with success. I'm running into an issue with my code design. I have a webservice class that I need to access from the remote CFC function and I want this webservice class to return the error message. Do you have any suggestions for setting error messages from this class? For example, I have a Register function which collects my local data and then validates against a third party webserice. If the webservice returns an error, I need to pass the error back so that my Ajax Service CFC can then return the error. Any suggestions?

5 Comments

Hi Ben,

It is always a great and instructive pleasure to read your articles, and I'm happy to have a question for you so I'll be able to thank you in the same time!

I'm trying to find the optimal approach to do something very specific in AJAX (From Ext to cfc's), and I believe that you are pretty damn good in finding optimal solutions :-)

Here it is:

For example, let say you are trying to get the status of an order displayed in an Ext formpanel, and then later, to apply the changes to that status in the database.

So far, I'm imagining this:

1) First, get the user status trough an ajax call to the method "component.cfc?method=getOrderStatus", given his ID;

2) later, when the user press the "apply" button, set the new user status trough another ajax call trough the method, "component.cfc?method=setNewOrderStatus", given his ID and his new status.

I could, of course, write some client-side rule to disable the "apply" button until the user changes something in the order status, but I would like to go one step further and rely on the method "setNewOrderStatus" to know if the submitted data is different from the one obtained trough "getOrderStatus". That is, of course, without submitting that information from Ext.

And it is why I naturally ended up in this post. In your example, you use the APPLICATION scope, so I believe there is one instance trough the whole application. I suppose you could have done the same trough the SESSION scope (altough only talking to yourself aint the most healthy scenario), but what about having some instance of the component only for a specific Ext component? That is, if the user open two order management components, each one have his own instance?

Thank you again!

Laurent

15,841 Comments

@Laurent,

While I don't fully grasp your situation, it sounds like it would be a good idea for the user to have something stored in their Session scope. Perhaps some sort of a representation of their Shopping Cart. Then, you could simply let your API calls be a proxy to the shopping cart, which would take care of the business logic internally?

5 Comments

@Ben,

Indeed, it is about associating some persistent coldfusion data to a specific Ext component (let's say a window in an Ext application, window that could be opened multiple times simultaneously), during the many ajax calls that may occur.

I suppose I need some unique ID generated by ext and transmited during every ajax call, and then store the data in coldfusion in a structure.

I made some draft, dirty approach here of how to keep some data between two ajax calls for a very specific ext component:

<cfcomponent>
	<cffunction name="GetOrderData" output="false" returnformat="JSON" access="remote">
		<cfargument name="Orderid" hint="the Id of the order">
		<cfargument name="ExtUniqueId" hint="the unique Id of the Extjs object">
		<!--- Initiate the "shopping cart" --->
		<cfparam name = "SESSION.ExtData.#arguments.ExtUniqueId#" default="#structnew()#">
		<!---do some business to retrieve the data--->
		<cfset LOCAL.response=...>
		<!--- Store the response in the shopping cart --->
		<cfset name = "SESSION.ExtData.#arguments.ExtUniqueId#.CurrentOrderData"=LOCAL.response>
		<!--- return the response --->
		<cfreturn LOCAL.response>
	</cffunction>
	<cffunction name="UpdateOrderData" output="false" returnformat="JSON" access="remote">
		<cfargument name="Orderid" hint="the Id of the order">
		<cfargument name="ExtUniqueId" hint="the unique Id of the Extjs object">
		<!--- compare the new data with the one retrieved from the shopping cart --->
		<cfif isdefined("SESSION.ExtData.#arguments.ExtUniqueId#.CurrentOrderData")
			AND evaluate("SESSION.ExtData.#arguments.ExtUniqueId#.CurrentOrderData") EQ POST.newdata>
			<!--- DO NOT DO ANY UPDATE (because nothing changed) --->
		<cfelse>
			<!--- DO THE UPDATE (because something changed) --->
		</cfif>
			<cfreturn true>
	</cffunction>
</cfcomponent>

I probably answered myself (with your help), but I'll enjoy your comments/criticisms if any.

Regards,

Laurent

15,841 Comments

@Laurent,

Ah, I didn't realize the data was UI-widget-specific. Yeah, for that you're definitely gonna need something unique to tie your API call to the widget. When in doubt, createUUID() is always a low-hanging piece of fruit. You could also create a session-specific ID generator or something:

session.getNewWidgetID()

... that keeps a session-based ID that increments or something.

In any case, it sounds like you're on the right track.

9 Comments

I really like the article and the discussion that followed. This helped me to create my own remoteproxy.cfc

One suggestion: In remoteproxy.cfc, suggest code be changed to:

<--- Create a new API response object. --->
<cfset LOCAL["Response"] = structNew() />
<cfset vgObj.Response["Success"] = true />
<cfset vgObj.Response["Errors"] = ArrayNew(1) />
<cfset vgObj.Response["Data"] = structNew() />

<!--- because of; upper case conversion issue with coldfusion structures and lower case (Javascript) issue: see
http://www.erichynds.com/coldfusion/a-quick-gotcha-with-cfml-and-json-serialization/
for more info
--->

9 Comments

OOPS cut-and-paste typo:

<--- Create a new API response object. --->
<cfset LOCAL["Response"] = structNew() />
<cfset LOCAL.Response["Success"] = true />
<cfset LOCAL.Response["Errors"] = ArrayNew(1) />
<cfset LOCAL.Response["Data"] = structNew() />

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel