Working with XML in ColdFusion - Struct versus XML functions

This post is more than 2 years old.

Earlier this week I worked with John Bliss on an interesting problem he had. Let me describe his issue, and the solution he came up, and what I learned from it.

John was trying to consume a web service. The web service demanded that a particular SOAP header be sent:

<soap:Header> <ApiUserAuthHeader xmlns="namespace"> <UserName>xxxxx</UserName> <Password>xxxxx</Password> <UserAccessKey>xxxx</UserAccessKey> </ApiUserAuthHeader> </soap:Header>

He created the header like so:

<cfset ApiUserAuthHeader = StructNew()> <cfset ApiUserAuthHeader.UserAccessKey = "xxxxx"> <cfset ApiUserAuthHeader.Password = "xxxxx"> <cfset ApiUserAuthHeader.UserName = "xxxxx"> <cfset AddSOAPRequestHeader(MyWebservice, "namespace", "ApiUserAuthHeader", ApiUserAuthHeader)>

So far so good, right? I've never actually used addSOAPRequestHeader, but just looking at his code it made sense to me. The result however was off:

<soapenv:Header> <ns1:ApiUserAuthHeader xmlns:ns1="namespace"> <item xmlns:ns2=""> <key xsi:type="xsd:string">PASSWORD</key> <value xsi:type="xsd:string">xxxxx</value> </item> <item> <key xsi:type="xsd:string">USERNAME</key> <value xsi:type="xsd:string">xxxxx</value> </item> <item> <key xsi:type="xsd:string">USERACCESSKEY</key> <value xsi:type="xsd:string">xxxxx</value> </item> </ns1:ApiUserAuthHeader> </soapenv:Header>

If you compare this to what the API requires, you can see they don't match. The first suggestion I had was to change how he worked with the structures. When you do:

<cfset s = {}> <cfset = "Ray">

to create a structure, ColdFusion will uppercase the struct key (name), to give you: s.NAME = "Ray". I recommended he try bracket notation, essentially:

<cfset s = {}> <cfset s["name"] = "Ray">

This didn't help either (although the resultant header did have the right case for the keys). I hashed this a bit more in my head and then I had an idea. He created the XML data pretty much the way I always did - with structure functions. Of course, ColdFusion also has native XML functions to create XML nodes/data. I never use them because the structure functions are simpler (to me!) and just plain work. In this case though switching to 'proper' XML manipulation functions worked perfectly for him:

<cfset doc = XmlNew()> <cfset doc.ApiUserAuthHeader = XmlElemNew(ApiUserAuthHeader, "namespace", "ApiUserAuthHeader")> <cfset doc.ApiUserAuthHeader.UserName = XmlElemNew(ApiUserAuthHeader, "UserName")> <cfset doc.ApiUserAuthHeader.UserName.XmlText = "xxxxx"> <cfset doc.ApiUserAuthHeader.UserAccessKey = XmlElemNew(ApiUserAuthHeader, "UserAccessKey")> <cfset doc.ApiUserAuthHeader.UserAccessKey.XmlText = "xxxxx"> <cfset doc.ApiUserAuthHeader.Password = XmlElemNew(ApiUserAuthHeader, "Password")> <cfset doc.ApiUserAuthHeader.Password.XmlText = "xxxxx"> <cfset AddSOAPRequestHeader(MyWebservice, "namespace", "ApiUserAuthHeader", doc)>

So I guess I don't have much to add here, but I'd be curious if this type of problem has tripped up others?

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA

Archived Comments

Comment 1 by marc esher posted on 6/5/2009 at 2:36 AM

it'd be telling to run this through tcpmon and watch the messages going across the wire. I'd imagine that would at least tell you what's different, though probably not *why* it's different when doing it one way vs. the other.

i must admit... this is intriguing!

Comment 2 by Russ posted on 6/5/2009 at 4:29 AM

The documentation for addSOAPRequestHeader says that the value "can be a CFML XML value". Guess they should have written "MUST be a CFML XML value".
Regarding the soap header generated using a struct, it looks like it is trying to handle two namespaces: the one you defined in addSOAPRequestHeader and a default XML namespace "".
You'd think that if you supplied a namespace in addSOAPRequestHeader, it wouldn't try to use a default XML namespace.
Could it be a CF bug?

Comment 3 by Raymond Camden posted on 6/5/2009 at 4:02 PM

@russ: Hmm, I don't know. But, shoot, wouldn't hurt to get the bug filed and let Adobe chew on it.

Comment 4 by John Bliss posted on 6/5/2009 at 4:19 PM

@russ @ray Bug filed.

Comment 5 by Ben Nadel posted on 6/5/2009 at 4:49 PM

I know this is a bit tangent, but when dealing with SOAP requests, I definitely find it much easier to manually build the XML request via CFSaveContent or CFXML. This is especially true when you start to deal with complex arguments that don't easily map (for one reason or another) to ColdFusion structures. Even though manual XML adds overhead, I found the overhead to be far easier than debugging the CF-to-SOAP implicit conversions.

Comment 6 by KC posted on 6/6/2009 at 8:29 AM

Ben: I totally agree with you, building the XML manually is so much easier - but I have experienced that doing a http post with xml is actually faster than invoking the soap webservice directly. I even remember reading someone did a performance comparison and pure xml was like 4 times faster

Comment 7 by Raymond Camden posted on 6/6/2009 at 5:50 PM

Technically when you invoke a web service, you ARE doing a http post with XML. Are you saying that not using cfinvoke/webservice= gives you a speed increase? Are you saying you generate the WSDL for the request by hand and just cfhttp it?