The existence of Application.cfc is a huge enhancement over Application.cfm. Whenever feasible, always use Application.cfc. It provides methods, such as onApplicationStart(), onRequestStart(), onSessionStart(), onCFCRequest(), and so on. Understanding how the Application.cfc works is a bit confusing, especially when you come from Application.cfm. In this blog, I'd like to focus on how variables-scope behaves on the requested page when onRequestStart() and onRequest() are implemented.
We all know that any variables-scope variables defined in Application.cfm are available on requested page. Now, how can we translate this behavior to Application.cfc? Here's the answer. Variables-scope variables should be defined in onRequest(). If we defined in onRequestStart(), it would work as well. However, the variables would not be available on request page until onRequest() is implemented. In other words, any variables-scope defined in Application.cfc live inside until onRequest() is implemented.
BONUS: When implementing Application.cfc's native methods, don't add output="false". Simply omit it, unless you know what you are doing.
Adding output="false" is equivalent to cfsilent, which suppresses contents generated by CF tags.
Adding output="true" is equivalent to cfoutput, which evaluates expression between pound signs; and outputs other contents.
The following script demonstrates how to implement authentication in SOAP header using ColdFusion.
We could pass username and password in simple value, but in this example we wanted to deliver them in XML.
NOTE: This blog has been updated with new code. The original one was written two years ago. :)
WebService.cfc
<cfcomponent output="false" hint="An example of Web Service.">
<cffunction name="echo" output="false" access="remote" returntype="string" hint="Accept text/string; and return it back to the caller.">
<cfargument name="message" type="string" required="false" default="" />
<cfset var response = "It is NOT a SOAP request." />
<cfset var namespace = "http://localhost/test/soap/" />
<!--- Check if it is a SOAP request and the request is legit --->
<cfif isSOAPRequest() AND authenticateRequest()>
<cfset response = "It is a SOAP request. Echoes: " & arguments.message />
</cfif>
<cfreturn response />
</cffunction>
<cffunction name="authenticateRequest" output="false" access="private" returntype="boolean" hint="Authenticate request.">
<cfset var result = false />
<cfset var namespace = "http://localhost/test/soap/" />
<!--- Get authentication node and return it as an XML --->
<cfset var auth_node = getSOAPRequestHeader(namespace, "authentication", true) />
<!--- Parse the authentication node content, which is credential --->
<cfset var cred_node = xmlParse(auth_node.xmlRoot.xmlText) />
<!--- Get the data from the XML --->
<cfset var username = cred_node.xmlRoot["username"].xmlText />
<cfset var password = cred_node.xmlRoot["password"].xmlText />
<!--- Verify the username and password --->
<cfif NOT compare("jsmith", username) AND NOT compare("abc123", password)>
<cfset result = true />
</cfif>
<cfreturn result />
</cffunction>
</cfcomponent>
client.cfm
<!--- Settings --->
<cfset end_point = "http://localhost/test/soap/WebService.cfc?WSDL" />
<cfset ws_args = {refreshwsdl=true} /> <!--- Force ColdFusion to refresh WSDL stub --->
<!--- Create Web service object --->
<cfset web_service = createObject("webService", end_point, ws_args) />
<!--- Construct XML document, which contains username and password --->
<cfxml variable="xml_doc">
<credential>
<username>jsmith</username>
<password>abc123</password>
</credential>
</cfxml>
<cfset namespace = "http://localhost/test/soap/" />
<!--- Add credential in the header --->
<cfset addSOAPRequestHeader(web_service, namespace, "authentication", toString(xml_doc)) />
<!--- Add SOAPAction:"" in the header --->
<!--- The header field value of empty string ("") means that the intent of the SOAP message is provided by the HTTP Request-URI.
No value means that there is no indication of the intent of the message.
Reference: http://www.w3.org/TR/2000/NOTE-SOAP-20000508/ --->
<cfset addSOAPRequestHeader(web_service, namespace, "SOAPAction", chr(34) & chr(34)) /> <!--- The value can be empty --->
<cfset message = "Hello, there!">
<!--- Invoke the service --->
<cfset response = web_service.advancedEcho(message) />
<!--- Get the response in SOAP --->
<cfdump var="#getSOAPResponse(web_service)#" />
I've been on and off working on SOAP using ColdFusion 9 (CF9) for a while. I found out that the support of SOAP was not fully implemented. For example, when a SOAP exception happens, CF9 throws SOAP fault in the form of regular ColdFusion exception. That's definitely not right! We should deliver the fault in SOAP format. Again, this broken support is happening on CF9. I didn't check on other CF versions.
To fix the situation, I wrote a process to intercept the CF exception, parse the SOAP fault, and deliver it in SOAP format. First, I utilize onError() in Application.cfc to intercept an exception. Second, there is a CFC to parse the fault that is in the exception.
NOTE: My code includes a user-defined function, arrayOfStructsSoft() from Nathan Dintenfass. Thanks, buddy!
Application.cfc
<cfcomponent output="false">
<cfset this.name = "soap" />
<!--- When error occurred, the SOAP throws a ColdFusion exception that contains text-based fault info.
That's the reason we need to parse and return it as a SOAP XML.
This method generates SOAP envelope that contains fault in the body. --->
<cffunction name="onError" returntype="void">
<cfargument name="exception" required="true" />
<cfargument name="eventName" type="string" required="true" />
<cfset var soap_envelope = "" />
<cfset var soap_fault = createObject("component", "SoapFault").init(arguments.exception['detail']) />
<cfset var fault_string = toString(soap_fault.getFaultNode("xml")) />
<!--- Remove XML header --->
<cfset fault_string = replaceNoCase(fault_string, '<?xml version="1.0" encoding="UTF-8"?>', '') />
<!--- Replace <Fault /> with <soapenv:Fault /> to follow ColdFusion naming conventions --->
<cfset fault_string = replaceNoCase(fault_string, '<Fault>', '<soapenv:Fault>') />
<cfset fault_string = replaceNoCase(fault_string, '</Fault>', '</soapenv:Fault>') />
<!--- Embed the fault to SOAP envelope --->
<cfsavecontent variable="soap_envelope">
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" <!--- SOAP 1.1 --->
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<cfoutput>#fault_string#</cfoutput>
</soapenv:Body>
</soapenv:Envelope>
</cfsavecontent>
<!--- Output the result as a SOAP XML.
So, the caller can receive and digest an exception/fault in SOAP format instead of ColdFusion native format. --->
<cfcontent type="text/xml; charset=utf-8" />
<cfoutput>#xmlParse(soap_envelope)#</cfoutput> <!--- xmlParse() converts string to XML --->
</cffunction>
</cfcomponent>
SoapFault.cfc
<cfcomponent output="false">
<cfset variables.instance = {} />
<cfset variables.instance["fault"] = {} />
<cffunction name="init" output="false">
<cfargument name="exception" type="string" required="false" default="" />
<cfif len(arguments.exception)>
<cfset variables.instance["fault"] = parseFault(arguments.exception) />
</cfif>
<cfreturn this />
</cffunction>
<cffunction name="parseFault" output="false" access="public" returntype="struct">
<cfargument name="exception" type="string" required="true" /> <!--- Receive an exception string from ColdFusion that contains SOAP fault information --->
<!--- REFERENCE: http://axis.apache.org/axis/java/apiDocs/org/apache/axis/AxisFault.html --->
<!--- The order should be matched with the one that ColdFusion outputs --->
<cfset var node_list = "faultCode,faultSubcode,faultString,faultActor,faultNode,faultDetail" />
<cfset var node_pos1 = 0 />
<cfset var node_pos2 = 0 />
<cfset var node_name = "" />
<cfset var node_array[1] = {element_name=node_name, element_position=node_pos1} />
<cfset var fault_info_pos1 = 0 />
<cfset var fault_info = "" />
<cfset var fault_node = "" />
<cfset var result = {} />
<cfset var i = 1 />
<cftry>
<!--- Build array that contains the element name and its position --->
<cfloop list="#node_list#" index="node_name">
<!--- Get the start position of each element --->
<cfset node_pos1 = findNoCase(node_name & ":", arguments.exception) />
<!--- Build a list based on what it is found --->
<cfif node_pos1>
<!--- Build a struct and set it to array --->
<cfset node_array[i] = {element_name=node_name, element_position=node_pos1} />
<!--- Increment array index --->
<cfset i = i + 1 />
</cfif>
</cfloop>
<!--- Sort the structure --->
<cfset node_array = arrayOfStructsSort(node_array, "element_position", "asc", "numeric") />
<cfloop index="i" from="1" to="#arrayLen(node_array)#">
<!--- Assume that all node in the list is found. In other words, no element_position is zero. --->
<cfset node_pos1 = findNoCase(node_array[i].element_name, arguments.exception) />
<!--- Assume that faultDetail is in the last list --->
<cfif node_array[i].element_name NEQ "faultDetail">
<!--- Get the end position and use the next element as a reference --->
<cfset node_pos2 = findNoCase(node_array[i+1].element_name, arguments.exception, node_pos1) />
<cfelse>
<!--- For the last element, faultDetail, use </pre> as the reference
The tag is found in cfcatch.detail --->
<cfset node_pos2 = findNoCase("</pre>", arguments.exception, node_pos1) />
<!--- If cannot find the tag, find "hostname:"
"hostname:" is found in cfcatch.faultString --->
<cfif NOT node_pos2>
<cfset node_pos2 = findNoCase("hostname:", arguments.exception, node_pos1) />
</cfif>
</cfif>
<!--- Get the whole string from "elementName:" to the next element --->
<cfset fault_node = mid(arguments.exception, node_pos1, node_pos2 - node_pos1) />
<!--- Get position of the current element value --->
<cfset fault_info_pos1 = find(":", fault_node) + 1 />
<!--- Get the element value --->
<cfset fault_info = trim( mid(fault_node, fault_info_pos1, len(fault_node) - fault_info_pos1 + 1) ) />
<!--- Insert in to a struct --->
<cfset result[node_array[i].element_name] = fault_info />
</cfloop>
<cfcatch type="any">
<cfloop list="#node_list#" index="node_name">
<cfif node_name EQ "faultString">
<cfset result[node_name] = cfcatch.message />
<cfelseif node_name EQ "faultDetail">
<cfset result[node_name] = cfcatch.detail />
<cfelse>
<cfset result[node_name] = "" />
</cfif>
</cfloop>
</cfcatch>
</cftry>
<cfreturn result />
</cffunction>
<cffunction name="getFaultNode" output="false" returntype="any">
<cfargument name="return_type" type="string" required="false" default="struct" />
<cfset var result = "" />
<cfset var root = "" />
<cfif arguments.return_type EQ "struct">
<cfset result = duplicate(variables.instance["fault"]) />
<cfelseif arguments.return_type EQ "xml">
<cfset result = xmlNew() />
<!--- Create a root element --->
<cfset result.xmlRoot = xmlElemNew(result, "Fault") />
<cfset root = result.xmlRoot />
<!--- For each element, create and populate the element --->
<cfloop collection="#variables.instance["fault"]#" item="element">
<!--- According to http://schemas.xmlsoap.org/soap/envelope/, the element name should be all lower case --->
<cfset arrayAppend(root.xmlChildren, xmlElemNew(result, lCase(element))) />
<cfset root[element].xmlText = xmlFormat(variables.instance["fault"][element]) />
</cfloop>
</cfif>
<cfreturn result />
</cffunction>
<cffunction name="getFaultCode" output="false" returntype="string">
<cfreturn variables.instance["fault"]["faultCode"] />
</cffunction>
<cffunction name="getFaultString" output="false" returntype="string">
<cfreturn variables.instance["fault"]["FaultString"] />
</cffunction>
<cffunction name="getFaultActor" output="false" returntype="string">
<cfreturn variables.instance["fault"]["faultActor"] />
</cffunction>
<cffunction name="getFaultDetail" output="false" returntype="string">
<cfreturn variables.instance["fault"]["faultDetail"] />
</cffunction>
<cfscript>
/**
* Sorts an array of structures based on a key in the structures.
*
* @param aofS Array of structures. (Required)
* @param key Key to sort by. (Required)
* @param sortOrder Order to sort by, asc or desc. (Optional)
* @param sortType Text, textnocase, or numeric. (Optional)
* @param delim Delimiter used for temporary data storage. Must not exist in data. Defaults to a period. (Optional)
* @return Returns a sorted array.
* @author Nathan Dintenfass (nathan@changemedia.com)
* @version 1, April 4, 2013
*/
private function arrayOfStructsSort(aOfS,key){
//by default we'll use an ascending sort
var sortOrder = "asc";
//by default, we'll use a textnocase sort
var sortType = "textnocase";
//by default, use ascii character 30 as the delim
var delim = ".";
//make an array to hold the sort stuff
var sortArray = arraynew(1);
//make an array to return
var returnArray = arraynew(1);
//grab the number of elements in the array (used in the loops)
var count = arrayLen(aOfS);
//make a variable to use in the loop
var ii = 1;
//if there is a 3rd argument, set the sortOrder
if(arraylen(arguments) GT 2)
sortOrder = arguments[3];
//if there is a 4th argument, set the sortType
if(arraylen(arguments) GT 3)
sortType = arguments[4];
//if there is a 5th argument, set the delim
if(arraylen(arguments) GT 4)
delim = arguments[5];
//loop over the array of structs, building the sortArray
for(ii = 1; ii lte count; ii = ii + 1)
sortArray[ii] = aOfS[ii][key] & delim & ii;
//now sort the array
arraySort(sortArray,sortType,sortOrder);
//now build the return array
for(ii = 1; ii lte count; ii = ii + 1)
returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
//return the array
return returnArray;
}
</cfscript>
</cfcomponent>
Most of time when we write code, we assume everything works like it should. However in reality, anything could happen. ColdFusion provides a way for us to handle exceptions gracefully. All these tags and the function are very useful, especially when the application interacts with third-party systems.
<cftry>...</cftry>
Wrap code that could potentially cause an exception with this tag.
<cfcatch>...</cfcatch>
This tag is placed inside <cftry> block. If an exception occurs, the tag would catch it; and we could handle it, such as logging the message or giving users a better error message.
<cfthrow />
This tag is placed inside <cftry> block. If something did not work as expected, we could throw a custom exception and let <cfcatch> handles it.
<cfrethrow />
This tag is placed inside nested <cfcatch> block. Nested <cftry>/<cfcatch> block provides a granular way to handle exceptions. If inner <cfcatch> could not handle it, we use <cfrethrow /> to send the exception to its parent.
onError()
This function is placed in Application.cfc. It catches any errors that are not caught by <cftry>/<cfcatch> block. So, whether you implement <cftry>/<cfcatch> block or not in your code, it would be wise that you implement this function.
ADDED:
I found the referring document above is not complete. It's missing <cffinally>...</cffinally>. The tag is used to wrap processes that needs to be run before exiting <cftry>...</cftry>. Keep in mind, it is always run whether there is an exception or not. Without <cffinally>...</cffinally>, any code after <cfcatch>...</cfcatch> would cause an exception.
Some tips for writing better code:
Implement onError() in Application.cfc
Wrap code with <cftry>/<cfcatch>
Use <cfthrow /> for creating custom exception
Use <cffinally>...</cffinally> for running addtional processes after cfcatch
If there is nesting cftry/cfcatch, we can utilize <cfrethrow />
In the old days, to measure how long a block of ColdFusion code is executed, we would use getTickCount(). Get the tick before and after the code in question; and subtract them. Newer versions of ColdFusion provide an alternative way to do the specified job. Wrap the code with <cftimer>...</cftimer>.
There is a downside using the tag, you will need to Enable Request Debugging Output in CF Admin. I also found that the "type" attribute is required instead of optional as stated on the documentation. It does not give us an error, but it will not show any results.
<cftimer type="outline">
<cfset i = 1 />
<cfloop index="i" from="1" to="100">
<cfoutput>#i#</cfoutput>
<cfset i = i + 1 />
</cfloop>
</cftimer>
NOTE: I knew that we could use i++ or incrementValue(i), but I just wanted to use i=i+1 for clarity. :)
I came across a bug/error logger called BugLogHQ. It includes a control panel where user can browse the logs. Setting it up is pretty easy. Follow its documentation page on http://www.bugloghq.com/docs.cfm.
A couple tips. This /bugLog/config/buglog-config.xml.cfm where you define the data source name (DSN), database type, username, and password. For security purpose, Personally, I would not recommend that you set username and password on this config file. If you chose to ignore the username and password, you would need to remove username and password attributes in /components/lib/dao/dbDataProvider.cfc. Otherwise, you would get an error. Another issue that I had during installation where I have MS-SQL Server 2008 installed using Windows authentication, this blog is certainly helpful.
Besides BugLogHQ, there are two other ColdFusion-based loggers I found. They are:
JSON has been gaining popularity to represent data in JavaScript applications. Below is an example of JSON data with some array and a ColdFusion parse function to convert JSON data to ColdFusion structure.
Without implementing proper technique, by default ColdFusion generates unnecessary whitespaces on result page. This will cause the page size bigger and consequently it will take more time to load the page. To suppress the whitespaces, we can enable whitespace management in CF Admin and/or do one of the following techniques:
<cfsilent>...</cfsilent>
Absolutely no output. Not even <cfoutput> nor <cfdump>.
Require closing tag </cfsilent>.
output="false"
One of the attributes in <cfcomponent> and <cffunction>.
It works like <cfsilent>.
<cfsetting enablecfoutputonly="true">
Only output things that are wrapped with <cfoutput>.
It works with <cfdump>.
The effect goes to <cfinclude> as well.
Matching tag, <cfsetting enablecfoutputonly="false"> is highly recommended.
Closing tag </cfsetting> is optional and does not have any effect.
Multiple declarations behave like a counter.
<cfprocessingdirective suppresswhitespace="true">...</cfprocessingdirective >
It reduces whitespaces, but it is not as effective as <cfsilent> or <cfsetting>.
<cfoutput> is not required.
Require closing tag </cfprocessingdirective>.
<!--- BONUS --->
<cfsetting showdebugoutput="false">
Disable debugging mode when Debugging Settings in CF Admin is enabled.
ColdFusion is built on top of Java; and it has plumbing to access Java classes. This is pretty cool. That means we can extend ColdFusion's capability.
Here is an example on how to utilize Java's Stack class. What we need to do is to create an object from Java class in question. Here, we created a Stack. As you can see, we can immediately call and utilize its methods.
<cfset objStack = CreateObject("java","java.util.Stack").init() />
<cfdump var="#objStack#" />
<cfdump var="#objStack.empty()#" />
<cfset objStack.push("a") />
<cfset objStack.push("b") />
<cfset objStack.push("c") />
<cfdump var="#objStack.empty()#" />
<cfdump var="#objStack.peek()#" />
<!--- The top element is always one --->
<cfdump var="#objStack.search('c')#" />
NOTE: Often, when we work with electronic payments, the third-party may require us to use certain algorithm for encryption. In this case, Java has more options to choose from than the ones already built in ColdFusion. So, take advantage of Java classes.
Want to insert a numerical value that contains decimal using <cfprocparam>, but the decimal portion is not showed up in the database? Most likely, we forgot to specify scale attribute. scale attribute is used to specify number of decimal places. If not specified, the default value is 0.
Have you ever needed to get data from Excel to ColdFusion to be manipulated? Before ColdFusion 9, reading records from Excel was cumbersome. <cfspreadsheet> makes the task handy. You can convert the records to spreadsheet object, query, CSV, or even HTML.
Here is an example on how to read an Excel spreadsheet and convert its rows to ColdFusion query.
Attribute rows is useful during development. Instead of reading all records in once, you can just output several records for testing/debugging. For more information on other attributes, please see the reference below.
ColdFusion 9 introduces a new operator called ternary operator. Unlike iif(), ternary operator looks cleaner and less confusing.
See the snippet below. Pay attention on the quotes.
<cfparam name="condition" default="true">
<cfset var_1 = "TRUE">
<cfset var_2 = "FALSE">
<cfoutput>
<!--- Use de() and quote will not evaluate the variable --->
immediate if: #iif( (condition), de('var_1'), de('var_2') )#
<br />
<!--- Use quote 'var_1' or de(var_1) will evaluate the variable --->
immediate if: #iif( (condition), 'var_1', 'var_2' )#
<br />
<!--- Use quote 'var_1' will not evaluate the variable --->
ternary operator: #( (condition) ? 'var_1' : 'var_2' )#
<br />
<--- No quote var_1 will evaluate the variable --->
ternary operator: #( (condition) ? var_1 : var_2 )#
</cfoutput>
Passing arguments/attributes via collection is very handy. However, you might get an unexpected error. This error could be caused by using argumentCollection or attributeCollection incorrectly.
See the code below and read the comments. I hope that helps! :)
<!--- Define a function to accept string and return it back to the caller --->
<cffunction name="echo" returntype="string" output="false">
<cfargument name="aString" type="string" required="false" default="" />
<cfreturn toString(arguments.aString) />
</cffunction>
<!--- Create a structure to hold an argument that will be passed to echo() --->
<cfset args_echo = structNew() />
<cfset args_echo.aString = "Hello World!" />
<!--- Call echo() and pass the argument via argumentCollection --->
<!--- NOTE: Notice that the argument name is "argumentCollection", instead of attributeCollection.
In other words, if it is a function/method then use argumentCollection. --->
<cfoutput>#echo(argumentCollection=args_echo)#</cfoutput>
<!--- Create a structure implicitly (require CF8 and up); and declare attribute name and its value --->
<cfset args = {var="#args_echo#", label="args"} />
<!--- Call <cfdump> and pass the attributes via attributeCollection --->
<!--- NOTE: Notice that the attribute name is "attributeCollection", instead of argumentCollection.
In other words, if it is a tag then use attributeCollection. --->
<cfdump attributeCollection="#args#" />