Because of a known bug in Flex 3's generation of AS3 classes from WSDL files that results in myriad encoding/decoding and type definition problems, my current Flex project has me alternating back and forth between my Flex-generated web service classes and the actual Flex WebService class. I end up having to use Flex's built-in WebService classes for calls to methods that require or return what are loosely termed complex objects.
The process of using the WebService class for these tasks is pretty generic. However, in my case, the Web Services with which I am working require an authenticated Token to be passed in the SOAP header to all methods, save for the one that actually does the authentication.
I've spent a good deal of time scouring the Internet for examples of adding SOAP headers to a Web Service call. Many of the posts and articles I found were been pretty solid. Most, however, dealt with fairly simple headers (i.e., passing in a string or two for a username and password). The Web Services with which I'm dealing require more than a couple of strings. I have to authenticate a user first and then use their authenticated token (which is good for 60 minutes) in my other method calls.
As with a few other posts, none of this is groundbreaking or overly complex. I merely wanted to post my experience in the event someone runs into a similar problem and needs an exampe of how to deal with this.
To start, here is a sample of how my SOAP Header should appear (most namespace crap removed for readability):
The Id, UserId, and ApplicationId properties of the Token object (children of the Toeken element in XML parlance) are Guids (Globally Unique Identifiers). A Guid, at its core, is nothing more than a string. However, because it's got a different type/class, I am not able to use a simple string. I have to pass in a string to my Guid constructor to set the Token properties properly.
Setting up the Token in Flex is pretty easy. I actually blogged about it previously on working with SOAP responses. Basically, I just store the token returned from the authentication routine in a globally available Token object (okay, it's not really globally available, this is a PureMVC app, so I get it via a proxy!).
When I use the Flex-built AS3 classes to interact with one of the application's Web Services, I just do something along the lines of the following:
public function getReports( rptReq:ReportRequest ):void
rs = new ResearcherService();
rs.addEventListener( FaultEvent.FAULT, oops );
rs.addrequestReport_header( token );
rs.addrequestReportEventListener( reportsRequestReceived );
rs.requestReport( rptReq );
Pretty easy. Just create a new service, add a couple of event listeners, and throw the token into the header. So long as the method only returns what Flex considers a "simple" value, I'm good to go. The RequestReport method (above), does just that. It only returns a simple object that has a few properties (strings, numbers and one array). Easy.
However, this same Web Service has another method, GetRequestHistory, that is pretty complex as regards what it returns (strings, numbers, file data, other objects). When I tried to deploy similar code to what's above, I get a SOAP decoding error, as Flex cannot resolve the type of the returned data (Adobe refuses to fix this issue in Flex 3, having said it's fixed in Gumbo, which it is but I can't use alpha software for a this application, the software changes too frequently -- thanks, Adobe!).
Anyway, in order to get the GetRequestHistory method to work, I need to use the "old school" WebService class (and then manually get to parse the SOAP response...fun!!!!). Here's the code to call GetRequestHistory with the WebService class, including setting up the SOAP header.
public function getRequestHistory():void
var qname:QName = new QName( "http://tempuri.org/", "Token" );
var hdr:SOAPHeader = new SOAPHeader( qname, token );
ws = new WebService();
ws.addHeader( hdr );
ws.wsdl = "http://mydomain.com/MyService.asmx?wsdl";
ws.addEventListener( FaultEvent.FAULT, oops );
ws.addEventListener( LoadEvent.LOAD, wsHistoryReady );
private function wsHistoryReady( evt:LoadEvent ):void
ws.GetReportHistory.resultFormat = "e4x";
ws.GetReportHistory.addEventListener( ResultEvent.RESULT, reportHistoryReceivedWS );
ws.GetReportHistory( studyName );
First, I create two variables, a qname (class: QName) and a header (class: SOAPHeader). The qname is a qualified name for the (SOAP) XML to be sent to the service. It defines a valid identifier for elements and attributes. You pass two attributes to the QName constructor: the URI of the namespace and the local name, both of which you would get from looking at the WSDL file. The local name is, so far as I can tell, the name of the object (or root XML element) you are passing. In my case, it's called Token (see the SOAP Header excerpt above for why/how this is the case).
Second, you pass the QName and object to the SOAPHeader constructor. Flex has now, in theory :), created the appropriate SOAP Header. All that's left is to apply it to the WebService instance, which is quite simple...you just pass the SOAPHeader variable (hdr in the code above) to the addHeader method and you're good to go.
Finally, load your WSDL and invoke your method! I've seen this step done a variety of ways but I prefer to add a LoadEvent listener to my WebService classes and only call my method once the WSDL has loaded (ws.addEventListener( LoadEvent.LOAD, wsHistoryReady );). Flex loads the WSDL really, really, fast and I've made calls to a WS method immediately after the loadWSDL() statement without any issues but this (waiting for the LoadEvent to fire) just seems a better practice to me.
The final note I'll make is that I had A LOT of trouble making the resultFormat of the WebService instance an "object" when dealing with the encoding/decoding issues I've mentioned. This wasn't really surprising to me because the Flex-generated Web Service classes couldn't decode the object returned from the SOAP, so why should the straight-up WebService class?
Next up, manually decoding the freaking complex object/XML to the appropriate ActionScript objects so I can use the returned data in my application. Ugh.