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 NCDevCon 2011 (Raleigh, NC) with:

My First ColdFusion Web Service

By Ben Nadel on
Tags: ColdFusion

I know, I know, I'm like a decade behind everyone else, but I JUST started playing around with web services. I have just never had to invoke or provide one before and so never experimented with it. I had kind of a rocky start with this example, but after some Googling and some Adobe documentation, I got it off the ground.

Application.cfc And OnRequest() Event Method

My first hurdle was getting my application to allow ColdFusion Components (CFCs) to be called directly. My application uses the OnRequest() event method in the Application.cfc ColdFusion component and as you may or may not know, doing so blocks the use of web services and flash remoting. To get around this, first I created a directory for my web services at "/resources/webservices/". Then, I applied a dirty little trick I picked this up from Ray Camden as explained in the first issue of the Fusion Authority Quarterly; I destroyed the OnRequest() event method for web service calls:

  • // Check to see if the target page is in the web services
  • // directory. If it is, then we want to delete the OnRequest
  • // event handler so the web service has access.
  • if (ExpandPath( GetDirectoryFromPath( ARGUMENTS.TargetPage ) ) EQ APPLICATION.ServiceFactory.GetConfig().GetFullPaths().WebServices){
  •  
  • // Delete the on request event handler.
  • StructDelete( THIS, "OnRequest" );
  •  
  • // Delete the on request end handler.
  • StructDelete( THIS, "OnRequestEnd" );
  •  
  • }

While I think Ray only recommended the OnRequest() event method removal, my OnRequestEnd() event method was causing issues (via the CFFlush tag) so I had to remove it as well. I placed this at the very end of the OnRequestStart() event method. Since that event method fires before the OnRequest() event method, it is able to successfully alter the pre-page processing event chain. Now, all calls to ColdFusion components (or any other files for that matter) located inside the "resources/webservices/" directory can be accessed directly. Thanks Ray!

BaseWebService.cfc ColdFusion Component

Then, I started my first ColdFusion component with remote access for use with web services. Stuff quickly started to break and I had no idea what was going on. I started getting errors like this:

Web service operation "GetCompliment" with parameters {Gender={}} could not be found.

But that was working a second ago! It just had a different name. Wait, how come it worked for this method and not that one? Wait, that method worked a second ago and now that I added an argument it no longer works! What gives!!!!

This was all very frustrating. Then I read that by calling the ColdFusion component build something called a "Stub" file. I am not 100% clear on how this all works, but supposedly this stub file defines what the web service does and how it works using the WSDL standard. The tricky part is that this stub file is basically only written once per file. If you go to alter the web service you will quickly find out that stuff breaks because the stub file is now out dated.

Apparently, there is something you can do in the ColdFusion Administrator to fix this (do a manual refresh or something), but I don't like going to the Admin for stuff. That is lame. I want to do it programmatically. Thanks to B. Prucell, I was able to rebuild the stub file using the ColdFusion Service Factory.

Now, since I don't really know what I am doing, I figured this is something I would want to build into the web services so that I could easily rebuild any web service stub file. To accomplish this, I created a base web service component that provides a default Initialization method and RebuildStubFile method:

  • <cfcomponent
  • displayname="BaseWebService"
  • output="false"
  • hint="This handles core web service features.">
  •  
  • <cffunction
  • name="Init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="Returns an initialized web service instance.">
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="RebuildStubFile"
  • access="remote"
  • returntype="void"
  • output="false"
  • hint="Rebuilds the WSDL file at the given url.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Password" type="string" required="true" />
  •  
  • <!---
  • Check to make sure this user has access to this
  • feature. This probably shouldn't be a remote access
  • function, but for the moment, I had no idea where
  • else to put it.
  • --->
  • <cfif NOT Compare( ARGUMENTS.Password, "sweetlegs!" )>
  •  
  • <!---
  • Rebulid this stub file for the web service using
  • the ColdFusion service factory. I picked this
  • tip up from:
  • http://www.bpurcell.org/blog/index.cfm?mode=entry&ENTRY=965.
  •  
  • Notice here that we are getting the requested
  • URL, which will be the web service coldfusion
  • component that was requested. Then we append
  • the "?wsdl" web service directive to the end
  • of the url.
  • --->
  • <cfset CreateObject( "java", "coldfusion.server.ServiceFactory" ).XmlRpcService.RefreshWebService(
  • GetPageContext().GetRequest().GetRequestUrl().Append( "?wsdl" ).ToString()
  • ) />
  •  
  • </cfif>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, I do some password protection; I don't want just ANYBODY rebuilding my stub files. Of course, this should probably not be remote access, but like I said, I don't really know what I am doing yet. This is my first web service experience. The thinking here though is that once I get this stub file built, I am very rarely going to change the BaseWebService component so even if the stub file is out dated, the RebuildStubFile() method will mostly likely be defined and available for use.

Fun.cfc ColdFusion Component

Ok, so now that I have my base web service ColdFusion component, I could allow my Fun web service to extend it:

  • <cfcomponent
  • displayname="Fun"
  • extends="BaseWebService"
  • output="false"
  • hint="Handles web services that are fun and experimental.">
  •  
  •  
  • <cffunction
  • name="GetCompliment"
  • access="remote"
  • returntype="string"
  • output="false"
  • hint="This returns a random compliment.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Gender" type="string" required="false" default="" />
  •  
  • <cfscript>
  •  
  • // Define the local scope.
  • var LOCAL = StructNew();
  •  
  • // Set up non-gender based compliments.
  • LOCAL.Universal = ArrayNew( 1 );
  • LOCAL.Universal[ 1 ] = "You are a great person.";
  • LOCAL.Universal[ 2 ] = "I wish I had more friends like you.";
  • LOCAL.Universal[ 3 ] = "You are too nice to people.";
  • LOCAL.Universal[ 4 ] = "You are my Go-To guy!";
  • LOCAL.Universal[ 5 ] = "I really appreciate what you do.";
  •  
  • // Set up female compliments.
  • LOCAL.Female = ArrayNew( 1 );
  • LOCAL.Female[ 1 ] = "You're hair really looks nice today.";
  • LOCAL.Female[ 2 ] = "Those are some great shoes!";
  • LOCAL.Female[ 3 ] = "You are a great woman.";
  •  
  • // Set up male compliments.
  • LOCAL.Male = ArrayNew( 1 );
  • LOCAL.Male[ 1 ] = "You look great! Have you been working out?";
  • LOCAL.Male[ 2 ] = "Dude, that's a rockin' mustache.";
  • LOCAL.Male[ 3 ] = "You are a great dude.";
  •  
  • // Pick the type of compliment we will be doling out.
  • switch ( ARGUMENTS.Gender ){
  • case "M":
  • LOCAL.Compliments = LOCAL.Male;
  • break;
  • case "F":
  • LOCAL.Compliments = LOCAL.Female;
  • break;
  • default :
  • LOCAL.Compliments = LOCAL.Universal;
  • break;
  • }
  •  
  • // Return a random compliment.
  • return(
  • LOCAL.Compliments[
  • RandRange(
  • 1,
  • ArrayLen( LOCAL.Compliments )
  • )
  • ]
  • );
  •  
  • </cfscript>
  • </cffunction>
  •  
  • </cfcomponent>

This web service component has one method, GetCompliment(). This web service takes an optional "Gender" argument and returns a gender specific or non-specific compliment. This presented a new problem. Everything worked fine and dandy when I tried to call the web service using the gender argument:

  • <!--- Invoke the web service as a dude. --->
  • <cfinvoke
  • webservice="http://..../webservices/Fun.cfc?wsdl"
  • method="GetCompliment"
  • returnvariable="REQUEST.Compliment">
  •  
  • <cfinvokeargument name="Gender" value="M" />
  • </cfinvoke>
  •  
  • <!--- Display compliment. --->
  • #REQUEST.Compliment#

However, if I tried to invoke this web service without the argument:

  • <cfinvoke
  • webservice="http://..../webservices/Fun.cfc?wsdl"
  • method="GetCompliment"
  • returnvariable="REQUEST.Compliment"
  • />
  •  
  • <!--- Display compliment. --->
  • #REQUEST.Compliment#

... it totally crashed and burned and give me this error:

Web service operation "GetCompliment" with parameters {} could not be found. <br>The error occurred on line 45.

After some hair pulling, some head pounding, and a good amount of Googling, I finally came across a posting by Steven Erat over at Talking-Tree. Apparently, for a web service you have to pass in optional arguments but tell the web service to omit them:

  • <cfinvoke
  • webservice="http://..../webservices/Fun.cfc?wsdl"
  • method="GetCompliment"
  • returnvariable="REQUEST.Compliment">
  •  
  • <cfinvokeargument name="Gender" value="" omit="true" />
  • </cfinvoke>
  •  
  • <!--- Display compliment. --->
  • #REQUEST.Compliment#

Notice that this time, my CFInvokeArgument tag uses the Omit attribute (and sets it to true). This invokes the web service and I guess does not pass in the argument, or I guess it passes it in (to jive with the WSDL document) but is just never used? No idea. All I know is that it works.

If you want to give it a go, the Fun.cfc web service is available at:

http://www.bennadel.com/resources/webservices/Fun.cfc?wsdl

Tweet This Groovy post by @BenNadel - My First ColdFusion Web Service Thanks my man — you rock the party that rocks the body!



Reader Comments

Ben,

It's probably just me, because I haven't yet found a need to do a CF webservice. The one service I ever provided was in .NET, for a desktop CMS. But, that is beside the point.

What I don't understand is why you had to go through so much trouble? For instance, why did you have to delete the OnRequestXXX() methods? Why provide them in the first place? It seems like a lot to go through... (and that is what I'm most intersted in)

The other comment I had was about the need to provide an optional argument. I know its not your fault, so don't take this as jab at you =). But, that just seems quite silly. <cfinvokeargument name="Gender" value="" omit="true" /> .... I mean, all that typing/clutter to accomplish nothing except omitting the argument that shouldn't have to be there in the first place! =)

It reminds me of the Java program that does absolutely nothing (as opposed to say, the CF program that does nothing). It's quite long, but it works well! =)

Reply to this Comment

A large application I work on uses web services to communicate with its CFCs which reside on a physically separate application server. Yes I am in hell. CF makes working with web services pretty easy, but there are a number of hair-pulling pieces such as using optional arguments and other issues like trying to debug errors without file names and line numbers. I ended up creating a web service wrapper that solves most of these issues for me. If you end up coming across a funky error that you can't figure out, shoot me an email - chances are I've come across it and fudged my way through it.

Reply to this Comment

@Rob,

Thanks for the offer. I am sure I will hit you up with some stumps :)

@Sam,

As far as the OnRequest() and the OnRequestEnd() event method, I provide them for other parts of the application. My OnRequest() event performs some security checks. My OnRequestEnd() performs some post page processing and logging type stuff. The problem is that these are not very web-service friendly. So, instead of changing the way I architect my application, I merely delete them if it's a web service call.

And, as far as the "omit" for the optional argument, I am sorry if I was not clear on this fact. Yes, it seems silly to include an argument that you are omitting. The problem is, though, that if you simply do not send the argument (leave out the CFInvokeArgument tag itself), the web service will crash. It says it cannot find the matching web service definition or something. You have to include the "omitted" argument so that ColdFusion can match the web service request to the WSDL definition. At least, that is what I think is going on. But I agree, it seems very silly.

Reply to this Comment

Tony,

While Sean's solution is definitely a nice, elegant one, I cannot use it. I have no application mappings in my applications and therefore cannot extend applicaition.cfc files that are in different directories.

Thanks for the link though. As I said, this is my first web service so I am looking to learn all I can.

Reply to this Comment

I am trying to do a simple web service in cold fusion and I am getting errors? Any ideas? This is the code?

<cfobject type="JAVA"
action="Create"
name="factory"
class="coldfusion.server.ServiceFactory">
<cfset RpcService = factory.XmlRpcService>
<cfset RpcService.refreshWebService("https://www.devcallnow.com/WebService/OneCallNow.asmx?wsdl")>

<cfinvoke webservice="https://www.devcallnow.com/WebService/OneCallNow.asmx?wsdl" method="Login" returnvariable="LoginToken">
<cfinvokeargument name="Service" value="1" omit="true" >
<cfinvokeargument name="GroupKey" value="2222" omit="true" >
<cfinvokeargument name="Pin" value="3333" omit="true" >
</cfinvoke>

Reply to this Comment

I kept getting not found..But thanks to the help of Paul from cf-talk this is what was discovered. I'm still not sure what all of it means.

Just to follow up on this, it seems that the webservice David was trying to consume using wsdl doesn't fit into the wsdl way of doing things in that it attempts to set one of the invoking arguments and pass that back as well as a complex object.

To get around this, I've told him to invoke the service using cfhttp instead of cfinvoke and parse the resulting XML to get the data he needs...

<cfsavecontent variable="xmlrequest"><soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<Login xmlns="https://www.onecallnow.com/WebService/">
<Service>1</Service>
<GroupKey>1111111</GroupKey>
<Pin>1111</Pin>
<LoginToken></LoginToken>
</Login>
</soap:Body>
</soap:Envelope>
</cfsavecontent>
<cfhttp method="post"
url="http://www.devcallnow.com/WebService/OneCallNow.asmx">
<cfhttpparam type="xml" value="#xmlrequest#">
</cfhttp>
<cfset SOAPXML = XMLParse(cfhttp.Filecontent.toString())>
<cfset response =
XMLParse(toString(SOAPXML["soap:Envelope"]["soap:Body"]["XmlChildren"][1]))>
<cfdump var="#response#">
<cfoutput>#response.LoginResponse.LoginToken#</cfoutput>

Reply to this Comment

Hi Ben & everyone reading this excellent article!

Once again you saved the day, Ben!
Just slightly modified the snipped to look like this:
ReFindNoCase("CMS/CFC",GetDirectoryFromPath(cgi.SCRIPT_NAME))...
(since I'm storing all my CFC's in that directory.)

Then off course, we have to keep in mind that onRequestStart won't be available to any cfc, but modifying the snippet further will probably handle this to make the method available (or not) for just some cfc's

Thank you!

Reply to this Comment

# if (ExpandPath( GetDirectoryFromPath( ARGUMENTS.TargetPage ) ) EQ APPLICATION.ServiceFactory.GetConfig().GetFullPaths().WebServices){
#
# // Delete the on request event handler.
# StructDelete( THIS, "OnRequest" );
#
# // Delete the on request end handler.
# StructDelete( THIS, "OnRequestEnd" );
#
# }

Hi Ben,
where would u put this code snippet ?in onrequeststart?
-Roman

Reply to this Comment

i got kind lost.
u r using StructDelete( THIS, "OnRequest" ); in onrequest?
+ if u use onrequest u will have to call explicitly file cgi.SCRIPT_NAME.

Reply to this Comment

@Roman,

I am sorry, I made a mistake. Put it in the OnRequestStart() method. That way, it can delete the "OnRequest" key before it has been executed.

Reply to this Comment

Thanks for this post. The note about including unused parameters with omit saved me a potential late night at work :)

Reply to this Comment

Ben first web service: 2006

Robert's first web service: 2008

Thanks for this Ben. It helped me out quite a bit. I'm on my way. Baby steps, baby steps.

Reply to this Comment

hi Ben, i want to write a web service for an online aboratory, please how do i start?

Reply to this Comment

How do you call this webservice (or any webservice) via a http-request in the browser without triggering the CFCExplorer in CFAdmin that tries to show the documentation of the webservice. I'm banging my head against the wall because the CFC is OK, the generated WSDL is OK, but calling it everytime like this (http://test.com/webservice.cfc) invokes the CFCExplorer. And as CFAdmin is not availableon production servers it throws a CF error...

HELP!

Reply to this Comment

Whenever you manually request a CFC manually through a browser (which translates an HTTP GET Request with no parameters), regardless if access is remote, public, or private, the request will redirect to the CFCExplorer to provide "self-documentation" on the CFC properties and functions.

When you invoke a ColdFusion webservice programmatically, you provide it an argument for URL with the ?wsdl query string. To invoke foo.cfc on company.com, you might use the url http://www.company.com/foo.cfc?wsdl. ColdFusion will generate and return a WSDL to the invoker. The invoker will then parse the WSDL, find the ServicePort (which itself is a URL indicating where the actual service endpoint, which in CF would probably be http://www.comapny.com/foo.cfc. The invoker will then send an HTTP Post Request with a SOAP Envelope bearing a SOAP Request to the service endpoint. The service endpoint processes the request and returns a SOAP Envelope containing a SOAP Response (or SOAP Fault).

The point is that in one case you request the cfc manually as a GET Request, and in the other case the invoker POSTs a SOAP Request to the to the cfc. The former triggers the CFCExplorer, the latter does not.

Reply to this Comment

@Steven and @Robert: Thanx!

It helped me some, but I'd really like to post some code here, but the CFC is a bit sensitive, so not sure howto do this! It's a simple component with an init function and a function that does something. This latter function can receive some parameters (of which some are mandatory and some are not) and the function (depending on a cftry/cfcatch-block) returns one numeric character for pass/fail. What am I overlooking, how do I tell the client how to call this webservice so that it works at his end (IE, that he can pass in the parameters and let the CFC do its thing)?

Am I making sense or just missing the really big point you've already pointed out?

Reply to this Comment

Nope, he's on a Filenet system just calling the webservice just like you described in your first reply to my comment. The client claims he cannot parse the arguments in the wsdl if they are of the type xsd:double (<wsdl:part name="parametername" type="xsd:double" />). He expects a xsd:numeric. In my CFC the type of the cfargument "parametername" is numeric and the returntype of the function is also numeric.

Is this built into CF8 or can I do anything about this? In other words, how can I get the wsdl:part to be of type xsd:numeric instead of xsd:double? I think this is the bottleneck...

Reply to this Comment

ColdFusion uses Apache Axis under the hood to implement WebService functionality. Complex types are frequently a topic of discussion in the ColdFusion community.

Tom Jordahl is the architect behind the Webservice implementation in CF, and he's on the Webservice W3C committee. He has many articles on working with complex types in CF. Here's a couple that might be useful here:

http://tjordahl.blogspot.com/2008/07/for-some-reason-i-havent-actually-even.html

http://tjordahl.blogspot.com/2008/04/reprint-consuming-web-service-complex.html

Reply to this Comment

In CF7, CF8 and in CF9 CF datatype numeric translates or is converted to xsd:double. In the CF livedocs and in the new CF9 help it nowhere states how this can be changed (http://livedocs.adobe.com/coldfusion/8/webservices_15.html and http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSc3ff6d0ea77859461172e0811cbec22c24-78b2.html). Builtin feature it seems, any ideas? Maybe save the wsdl, change the datatype and havemy client call the wsdl directly instead of the CFC? Not fun if the CFC was to change sometime in the future...

Reply to this Comment

@Sebastiaan,
Hmm, that's beyond my expertise. However, I had some issues in my web service with some calls and decided to leave out the type parameter. If I did that, everything worked ok. I may get slapped by the professionls (of which I am not one) but that's what I did.

Also, a brief look at Ben Forta's MX7 book equated the cf datatype of "numeric" with the wsdl data type of "SOAP-ENC:double". Don't know if that helps or not.

-Robert

Reply to this Comment

>> "Maybe save the wsdl, change the datatype and havemy client call the wsdl directly instead of the CFC?"

The wsdl does not implement the webservice, it only describes it. Changing the datatype in the wsdl will only break the webservice if the implementation (the CFC) does not operate as described by the wsdl.

Reply to this Comment

@Steven: OK, that clears out that option, I guess my client will have to adapt his code to accept xsd:double...

@Robert: The datatype conversion you mention was also what I was pointing to in my two links. This hasn't changed since CF6...

Reply to this Comment

Also, just wanted to throw in there that you don't have to invoke a web service using SOAP. Wasn't sure if this option was talked about. As far as invoking a CFC via the URL, you should get around the CFC Explorer if you add in the "method" parameter:

./webservice.cfc?method=someMethod

It sounds like you guys are doing SOAP, but just wanted to add those two cents.

Reply to this Comment

@Ben: Thanx, I now can test m webservice properly ;-)

@All: We ended up changing the datatype from numeric to string, because a xsd:double delivers 2,0 in the call to the CFC when reading the WSDL and the CFC coughs immediately that the argument not is of type numeric. Sigh...

Anyways, we have it working now, that's the most important thing! Thanx for all the good advice and the back-and-forth on possible solutions ;-)

Reply to this Comment

Ben, I need some clarification. In the listing for your Fun service (Fun.cfc ColdFusion Component), the function GetCompliment has an argument named Gender, with an access attribute of "remote", but a required attribute of "false", and with a default value. According to the ColdFusion 8 docs, all arguments to a ColdFusion web service function are considered required. The attribute required="false" is ignored. Am I correct on this?

Reply to this Comment

@Christopher,

When it comes to invoking web services, all arguments are required as far as the need to be defined; however, the CFInovkeArgument allows you to both define and omit an argument, letting it's default value be used.

Take a look at the last code sample - in it, I am defining the argument, but I am using the "Omit" attribute to NOT send a value.

So, yes, they all need to be defined in the invoke, but they don't need to be *passed*.

Reply to this Comment

Thanks for calling my attention to that which I had missed. I now know which tack I should take with my service. Good day, sir.

Reply to this Comment

Ben,

I think I figured out my problem. Just needed to match up the post variables with the CFC arguments. Thanks anyways.

Reply to this Comment

@Jordan,

I don't believe you can post WSDL-based (SOAP) web services using URL parameters. You have to create an actual SOAP request. If you want to use the URL-style post, you don't want to do SOAP - just call the method you want and then each argument as a name-value pair in the URL query string.

Reply to this Comment

Hi Ben,

I am converting some web services that I wrote in Java with Axis 1.1 some years ago to Cold Fusion webservices.

I need to use the same WSDL that was provided to our clients. Now CFCOMPONENT has an option that allows you to specify the WSDL file:

wsdlFile = "mywsdl.wsdl"

However, when I try to invoke my web service like this I am getting the standard error that we have all seen:

Web service operation MywebService with parameters xyz cannot be found.

Are there any gotcha's when using a manually assigned wsdl file like this? Is anyone else using this technique where you are using wsdlFile="".

Would be grateful for any insights.

Khalid

Reply to this Comment

Does anyone know of a way to dynamically change the endpoint of a webservice when using the cfhttp method of calling a wsdl? With createobject, I know you can do:

<cfset ws = CreateObject("webservice", "#variables.wsdlLoc#", "myPORT")/>
<cfscript>
ws._setProperty("javax.xml.rpc.service.endpoint.address", "https://newendpoint.com/test.jws");
</cfscript>

which dynamically changes the endpoint - but how is this done with cfhttp to the wsdl?

Reply to this Comment

Here's a good one...

Let's say I have a basic cfc with 2 methods

setSomeValue()
This method takes 1 arguments and sets it to the variables scope.

getSomeValue()
This method returns variables.someValue (whatever was set in the previous method)

REALLY straight forward stuff here.

This works if the methods are set to PUBLIC, but switch them to REMOTE and change the invocation code and suddenly getSomeValue has nothing to return.

I can't find anywhere that states that webservice cfcs can NOT store values. Can ANYONE help me out here?

Thanks again!

Reply to this Comment

@Khalid,

That's very interesting - I was not aware that there was an option to provide an already existing WSDL file. I will have to look more into that; at this time, I do not have any good advice.

@Jim,

To be honest, I am not even sure what an endpoint is. I typically make SOAP-based web service calls with raw XML and CFHTTP. Sorry I cannot be more help at this time.

@Ryan,

The problem with this is that the CFC is created fresh for every remote access method call. As such, the data from the SET method is not there when the GET method is called.

If you want to do this, you will have to cache the remote CFC instance and then wrangle the remote requests manually. Of course, if you are making SOAP-based requests, I don't think this can be done, since ColdFusion does so much SOAP-based auto-wiring for you. Typically, this kind of manual routing can only be done with URL-based method calls.

Reply to this Comment

Ben - Makes total sense. I had a feeling that since CF converts the CFC to a WSDL document, you lose a lot of functionality that CFCs usually provide.

I suppose when I go to refactor my app, I should highly consider a RESTful approach. That does seem to be the "proper way to do it" these days.

Thanks for the help!

Reply to this Comment

No prob Ben, thanks for responding. I have another question for you or anyone who might be watching. When I invoke a webservice that was built with java and it has a complex object and the stubs ColdFusion creates is 99% correct and the resulting XML request just needs to be tweaked a very small amount (remove a typing) for it to process..which I've validated using soapUI. Any idea on how this can be done? Can I create a version of the stub myself and use that instead of CF recreating the stub? Any guidance is greatly appreciated.

Reply to this Comment

@Jim,

Typically, with XML-based web services, I generally craft the XML manually and post it using CFHTTP; then, I manually parse the XML that comes back in the response. While the CF-SOAP stuff is very cool, I have found it to be a bit too complicated sometimes. And, as much as it might sound backwards, dropping down into the raw XML tends to simplify things for me.

Reply to this Comment

Ben, What are your thoughts on "RESTful" webservices (I still have a hard time calling them webservices since they aren't REALLY).

I'd be interested in reading your approach to building them with CF.

Reply to this Comment

@Ryan,

I'm definitely a fan of URL-based web services. I'll resist calling the RESTful since that's not really the kind that I build. When it comes to ColdFusion, you acn invoke both CFM and CFC templates using URL invocation:

SomeObject.cfc?method=sometMethod

... and:

some_template.cfm?method=someMethod

They both work well and are easy to test. CFC-based invocation has some additional data handling that it does for you, but also adds some complexity as well (that is often easier to handle in a CFM-based approach).

I am happy to use either one, usually called using a standard HTTP request (either from CFHTTP or from an AJAX request).

Reply to this Comment

@Ryan,

As far as what a "Web service" actually is, I don't think there is a particular format that is required to make it a web service. SOAP, REST, whatever - they are all just ways to allow two machines to communicate (which I believe is all the definition of a web service is).

Reply to this Comment

Hi!

I'm trying to invoke my webservice with predefined data. Tried it on cfhttp, on the url and via the cfinvoke method. Also added the code that resets the wsdl file, that Ben has posted here. I get funky errors suddenly on arguments that haven't been defined, which are defined fine. This happened with private functions inside the cfc as well as with the new method I made to 'upload' xml data to the webservice.

Stuff like:
coldfusion.runtime.UndefinedElementException : Element DATATYPE is undefined in ARGUMENT.]

While it is defined nicely via:
cfinvokeargument name="xmlData" value="#xmldata#" omit="true">
<cfinvokeargument name="DataType" value="UserSchedule" omit="true">
<cfinvokeargument name="DataRecipient" value="1" omit="true">

Tried as well with removing the omit attribute.

Inside the function I have them defined as such:
<cfargument name="xmlData" type="string" hint="The XML Data">
<cfargument name="DataType" type="string" hint="The type of the sent Data (which XMLdata is being sent)">
<cfargument name="DataRecipient" type="Numeric" hint="The ID for which instance this data is for">

At the moment I have my inner method call disabled, since it also generated an error on the argument scope. That was a function that would generate a xml to return with a result = ok or not ok kinda thing (response xml). As I had to pass the 1 for ok or 0 for not ok, I also got an error on that argument. So I wonder whether this is a wsdl error, or a "Steven has to learn to write cfc's error"

Thanks for the help!

ps. could post the url, but rather don't want too much traffic on it as it's a dev server.

Reply to this Comment

and never mind.. I found the bugger. had nothing at all to do with the webservice, but with me making big typos with only 4 hours sleep. Logical!

as above:
Element DATATYPE is undefined in ARGUMENT.

while the argument scope isn't called argument... but arguments (with an S!!)

So never mind, solved by itself now.

As for the numeric going to double, I simply have made it to string, seeming to be the only solution for now.

Nevertheless thanks Ben/B. Prucell for the great rebuild function.. that helped too.

Reply to this Comment

I am attempting to invoke a method on a webservice however it requires that I pass in an authorizationHeader. However I cannot figure out how to add it to the soap request headers.

http://www.codebump.com/subscriptions/tutorial.aspx

<cfscript>
zipWSObj = createObject('webservice', 'http://codebump.com/services/placelookup.asmx?wsdl');

addSOAPRequestHeader(zipWSObj, "", "SessionID", 'ezAlKUQ+iaodn6Ra0fPXikZPL495zH6iuqnx0HZvV0dtDVIz2Sh8pRlmJOyRr+mqHmfwiWr64AyfCSWCxowpUxm4yh+WB2Vo');

stWSArgs = {place="Montrose", state="CO"};
</cfscript>

<cfinvoke webservice="#zipWSObj#" method="getPlacesInside" argumentcollection="#stWSArgs#" returnvariable="theResult" />

<cfdump var="#theResult#">

Here is the adobe docs that states adding a soapRequestHeader : http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions_a-b_03.html

Any ideas on how to pass in this SessionID as a SOAP header so I can invoke methods of the web service.

Sorry for the long post any help would be appreciated.
Tim

Reply to this Comment

1) Could you please tell what is the difference in invoking a third party webservice via 'cfinvoke' and 'cfhttp'?
Is there any specific point to be taken care while passing xml as argument.

2)Why do we append wsdl in some cases and in some other not?

3) The eror 'Web service operation "" with parameters {} could not be found' was obtained for me when I tried to call a cfc written as service which just returns 1. It doesn't have any argument. Why is it so?

Many Thanks

Reply to this Comment

@Tim,

To be honest, whenever I work with SOAP, I use raw XML, posted with CFHTTP to make web service calls; when you do that, it's somewhat more straightforward as to where the authorization headers go.

I don't have much experience with the using the web service wrapperd as I have found them to be a bit tricky to work with. When they work, they make things easier; when they don't, it's super frustrating.

@Jasmine,

CFHTTP requires you to build the SOAP xml. Using CFInvoke creates all the SOAP xml for you; this is why it needs the WSDL file - so it can see how to translate your arguments into the appropriate SOAP xml packet.

When you get a "could not be found" error, it's typically because you are wither passing in the wrong number of arguments or because the WSDL file is outdated. The WSDL file gets cached; so, if you change the signature of the web service, you need to refresh the WSDL.

I believe that the CFInvoke tag has a refreshwsdl="true" attribute that will allow you to refresh the WSDL during development.

Reply to this Comment

Thanks ben.One more question:Sometimes, the xml sent as argument to function seems to have space appearing before the xml version tag (even if we use trim).Hence when we validate it with xsd before passing it to the webservice, it is not becoming success. Any suggestion is appreciated.

Also, regarding the 'could not be found error', that was encountered the very first time when the service was called which was written without any argument.

Reply to this Comment

Hello! I've been attempting to consume an ASP.NET web service in CF9 via CFINVOKE, but all I am coming up with is:

Cannot generate stub objects for web service invocation.
Name: [server redacted] Servicehost/ProductInquiryService.svc?wsdl. WSDL: [server redacted] Servicehost/ProductInquiryService.svc?wsdl. java.io.IOException: Emitter failure. Invalid endpoint address in port tcpEndpoint in service ProductInquiryServiceLocator: net.tcp://dc1srvcamss001.circlesolutions.com/ServiceHost/ProductInquiryService.svc

I've done a ton of Googling and come up with nothing. Any suggestions? I know nothing at all of ASP.NET, but if it's some tweak needed on the service end, I can pass information on to the people who maintain it.

Thanks!

-John R.

Reply to this Comment

@John,

Very odd. It sounds like there is some problem on the other end. When you go to the URL:

....Servicehost/ProductInquiryService.svc?wsdl

Does the WSDL file render properly? If not, then I think there is nothing you can do. If the WSDL file does show fine, then perhaps you might want to try using the CFHTTP approach:

http://www.bennadel.com/blog/1809-Making-SOAP-Web-Service-Requests-With-ColdFusion-And-CFHTTP.htm

... rather than the CFInvoke approach. CFHTTP adds a bit of manual overhead; but, with that overhead comes full control over how the SOAP XML is defined and sent.

Reply to this Comment

@Ben Nadel, thanks, I'll give that a try. The WSDL renders okay, but cfinvoke just doesn't cut the mustard.

The ASP.NET team are working on retooling their files, too, so hopefully between us we can work it out.

-John R.

Reply to this Comment

Is it really true that all these years later that CFINVOKEARGUMENT with the OMIT attribute is the only way around this? In a world of CF9, where (almost) everything is doable in CFSCRIPT natively could it be this is the only option?

Reply to this Comment

Thanks, a very interesting article even several years after you wrote it.
I've now got my first web service up and running, and after the same problems you had with that "stub", found a very simple (if well-hidden) solution.
<cfinvoke
refreshWSDL = "yes"
....
>
Introduced in CF8, so I'd guess it didn't exist when you needed it.

Reply to this Comment

@Nathan,

That's a good point. Tags FTW! ;)

@Jane,

Yeah, unfortunately that was not available at the time. It has since become a super valuable tool in web service deployment!

Reply to this Comment

Thank you so much for this. I've had this problem before and just gave up using ColdFusion's web service tag. I'm Very happy to know a simple solution exists.

Reply to this Comment

@Hussein,

Awesome my man; also, I hope you saw the comment a few above this about the refreshWSDL="true" attribute.

Reply to this Comment

@Ben,

Yes I saw it but I don't totally understand it. I'm happy that it solves this problem with even less code though. No arguments there.

Reply to this Comment

@Hussein,

Cool my man. It's not something that you'd want to be using in production - it's really only for when you're building and debugging the web service (it has a performance cost overhead of constantly refreshing the WSDL). Good luck!

Reply to this Comment

I keep getting this error :

coldfusion.jsp.CompilationFailedException: Errors reported by Java compiler: Found 1 system error: *** Semantic Error: The input file "C:/ColdFusion9/stubs/WS-793786476/spacews/ConopsWS.java" was not found. .

CFM Page :
<!--- Invoke the web service as a dude. --->
<cfinvoke webservice="http://127.0.0.1/SPACEWS/BaseWebService.cfc?wsdl" method="RebuildStubFile">
<cfinvokeargument name="Password" value="M!a1tank" />
</cfinvoke>

<!--- Invoke the web service as a dude. --->
<cfinvoke webservice="http://127.0.0.1/SPACEWS/ConopsWS.cfc?wsdl" method="test" returnvariable="Request.Data" refreshwsdl="yes">
<cfinvokeargument name="POIGenInfoID" value="1" />
</cfinvoke>

<!--- Display compliment. --->
<cfdump var="#Request.Data#"/>

WS page:
<cfcomponent displayname="CONOPSWS" extends="BaseWebService" output="false" hint="Handles web services that are for the CONOPS">

<cffunction name="test" access="remote" returntype="string" output="false" hint="test.">
<!--- Define arguments. --->
<cfargument name="POIGenInfoID" type="numeric" required="true" default="" />

<cfscript>
return('a');
</cfscript>
</cffunction>

</cfcomponent>

Reply to this Comment

I was trying to read the image From webservice which was developed in .net but i was getting the error,
Here By i have attached My code and error Message

Code:
<cfinvoke
webservice="http://localhost:1648/DisplayImage.asmx?wsdl" method="GetData" returnvariable="Image" >
<cfinvokeargument name="ImageTypeId" value=1 omit="yes"/>
<cfinvokeargument name="ImageYr" value=2003 omit="yes"/>
<cfinvokeargument name="CADAccountNumber" value="00000132" omit="yes"/>
<cfinvokeargument name="CadID" value=94 omit="yes"/>
<cfinvokeargument name="Serial" value=1 omit="yes"/>
</cfinvoke>

<cffile action = "readBinary" file = "#Image#" variable = "aBinaryObj">

<cfset myImage = ImageNew(aBinaryObj)>
<cffile file="#myImage#" action="write" output="c:\Ntemp\Image.jpg">
<cfimage action="writeToBrowser" source="#myImage#">
<cfoutput>The Output Value is #myImage#</cfoutput>

Error:
Web service operation GetData with parameters {CADAccountNumber={00000132},ImageYr={2003},CadID={94},Serial={1},ImageTypeId={1}} cannot be found.

Reply to this Comment

I am using the webservice for retrieving the image from server,That was an object format ,i need to convert into image format ,please do needful

Reply to this Comment

@Darrin,

If you are on a system that allows for the CFInvoke attribute, refreshwsdl="yes", which it looks like you are from your code, I would get rid of the Rebuild approach that I was using. This post is from before the refreshwsdl="yes" was available. Now that ColdFusion offers a native way to rebuild the stub files, I would definitely use it and discard the old method.

@Thangapandyan,

Why are you using omit="yes" on all of your invoke argument?

Reply to this Comment

@Ben,
Hi thanks for your comments still i am getting error ,the error details are

Unable to read WSDL from URL: http://localhost:1648/DisplayImage.asmx?wsdl.
Error: java.net.ConnectException: Connection refused: connect.

Reply to this Comment

Unable to read WSDL from URL: http://thoughtdeep/FetchDetailedImages/DisplayImage.asmx?wsdl.
Error: 401 Unauthorized.

Reply to this Comment

Thanks for the code. It seems the most complete example I've seen so far.

Somehow, I tested the code and I get this error. (In the exception log)

https://example.com/Fun.cfc?wsdl

"Error","jrpp-4241",--snip--,"Variable MODE is undefined. The specific sequence of files included or processed is: x:\wapproot\Fun.cfc, line: 103 "

I tried to look at the generated java code, but can't find it so don't know what line 103 is.

Oddly,
https://example.com/Fun.cfc?method=GetCompliment
works OK.

I'm on Coldfusion standard 8, IIS 7. Do you have any advice?

Thanks.

Reply to this Comment

By chance, I found that my problem was because of Application code. There was a code in Application.cfm that assumes variable 'MODE' exists in the application scope. Thanks any.

Reply to this Comment

By chance, I found that my problem was because of Application code. There was a code in Application.cfm that assumes variable 'MODE' exists in the application scope. Thanks any.

Reply to this Comment

Once again, a Ben Nadel post to the rescue.
That omit="true" part is one of the nastiest things I've run into in CF to date. Why the hell would you require the argument to be passed if you're just going to omit it? Especially when its not "required"?

Oh well. Thanks again Ben for posting CF related stuff.

Reply to this Comment

Hi,
I am trying to make a webservices call to a .net webserver.
Does anyone have an idea how to do it.
Please help.
Regards,
Purnima

Reply to this Comment

Hi,

Short version - how do you get a .cfc component
to recognize application.cfm?

Long version - Ben Kim's comment of 1/19/11 makes it sound like he's successfully getting application.cfm to be read by his web service. When I've put global variables into application.cfm, my web service doesn't recognize them. I have to cfinclude the file. I thought maybe just changing the name to application.cfc would work, but that didn't do it.

Many thanks!

Reply to this Comment

@Mabel Liang,

Are you still asking, or did you figure it out? My company has a general, overall Application.cfc which is used across the board on all parts of the portal/site, and then we put Application.cfc within the folder that holds that part of the portal that we are developing on. In addition to this, we add an app.cfm file. Is it possible that something like this might work for you?

Reply to this Comment

@Anna,

Hi!

I'm still looking. So you're saying that just by having the application.cfc OR the application.cfm in the same folder as your web service (which is in a different .cfc file), you're getting the file(s) read in and the variables recognized? That hasn't worked for me.

Thanks!

-- Mabel :-)

Reply to this Comment

@Mabel,

I'm not sure if you can have more than one Application.cfm...maybe? It has been so long since I have worked with Application.cfm, I don't remember. I guess, though, you wouldn't want to change it mid-stream anyway, though. I could see how that could cause some probs.

Ok, according to my research and my resources, you can do the above, so disregard that part, and the question.

Anyway, here's what I was saying about the set up, especially as it relates to the Application.cfc file. You have one Application.cfc (or cfm) in the root file, then separate ones in separate directories. We also have separately an App.cfm file as well. the App.cfm file is in the same directore as the current template, which also houses one of the Application.cfc file as well. I think a lot of the variables are set in the separate App.cfm file. But since I re-read your post, and you mentioned cfincluding, I believe that there is a cfinclude at some point in the code where one of the files is cfincluded. I think I remember seeing that.

So, you did try to rename the Application.cfm to Application.cfc? Maybe the variable setting needs to be placed within a specific method for them to be recognized? Or maybe it is one of those variables that needs to be preceded by "this."?

Reply to this Comment

@Anna,

Thanks! I'm fairly new to Cold Fusion and have just been doing a fair amount of reading to understand application.cfc. I did rename the file. But it looks like I have to put the variables into specific methods, so I guess I'll have to learn more about it.

I was hoping someone could tell me if there's a way to get application.cfm recognized, or if things just don't work that way.

Thanks so much for you help!

-- Mabel :-)

Reply to this Comment

@Mabel,

No problem! I am probably just right behind you, if behind you at all. Half the time, I don't even respond, because I can't even come close to being sure if I have the right answer. But I just thought I would throw those out there since I've seen that before in the way my company handles their Application.cfc. I could still be wrong here, but just wanted to throw my .02 in there, in case it helps. :-)

Reply to this Comment

Hi, I have some questions about coldfusion webservices.
I tried to connect a remote Webservice with this code:
XML Request:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope….

<cfhttp url="xxx/xxx.cfc?WSDL">…

It is working.

Now I want to get an object. Here my example:
<cfscript>
ws = CreateObject("webservice", "http://xxx/xxx.cfc?WSDL");
result = ws.getSomething(system_name= "xxx",system_no="xx",number="xxx");
</cfscript>

At first it's working only in my network. Furthermore I do not know how I get all data from this object like with a loop.

Can you help me?

Reply to this Comment

@Minns,
here i am showing one example of how to access the webservice function usnig cfscript method with array

<cfscript>
stUser = structNew();
stUser.variable1 =value1;
stUser.variable2 =value2;
ws = createObject("webservice", "http://x.com?wsdl");
myReturnVarMultiple = ws.getMultipleURL(stUser);
</cfscript>
<cfif isdefined("myReturnVarMultiple") and myReturnVarMultiple neq "">
<cfset iPath = myReturnVarMultiple.getEnc_Multiple_URLResult()>
<cfif isdefined("iPath") and iPath neq "">
<cfset arImg = #iPath.getArrayOfString()#>
<cfloop from="1" to="#ArrayLen(arImg)#" index="i">
<cfset fin = #arImg[i].getString()#>
<cfloop from="1" to="#ArrayLen(fin)#" index="j">
<cfset img = #fin[1]#>
</cfloop>
<li>
<img id="back" src="<cfoutput>#img#</cfoutput>" title = "<cfoutput>
</li>
</cfloop>
</cfif>
</cfif>

Reply to this Comment

Hi Ben,
I've opened up http://www.bennadel.com/resources/webservices/Fun.cfc?wsdl in my browser and can see on line 2 of the auto generated WSDL is:
<!--WSDL created by ColdFusion version 8,0,1,195765-->
This strikes me as information that i'd not like to give out to the world. Do you, or any of your readers, know of any way to surpress the CF version from the auto generated WSDL? Using the "wsdlFile" property is an option but manually updating the WSDL when the CFC changes doesn't appeal.
I've also posted this on question on: http://forums.adobe.com/thread/991221

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.