Using an Embedded WSDL with Flex’s WebService API

Recently I was helping a customer figure out how to use an embedded WSDL with Flex’s WebService API. One scenario in which this is needed is when the actual WSDL is not available at runtime. In this case the application must contain the WSDL instead of request it at runtime. The Flex WebService API today only supports loading the WSDL over the network at runtime. Beginning in Flash Builder 4 the Service wizard generate code that internally use the WebService API. So no matter how you integrate with a SOAP Web Service in Flex, you need the WSDL accessible via a URL at runtime. This wasn’t possible for the customer I was working with so we figured out a way to actually embed the WSDL into the application. Here is what we did…

I used my Census SOAP Service as a simple service to test this with. In this case, my WSDL is publicly available at: http://www.jamesward.com/census2-tests/services/CensusSOAPService?wsdl

First, I created a new Flex project and saved the WSDL into the src dir of the project. I built a simple test program using the WebService API directly:

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
			   xmlns:s="library://ns.adobe.com/flex/spark">
 
	<fx:Declarations>
		<s:WebService id="ws" wsdl="http://www.jamesward.com/census2-tests/services/CensusSOAPService?wsdl"/>
	</fx:Declarations>
 
	<s:applicationComplete>
		ws.getElements(0, 50);
	</s:applicationComplete>
 
	<s:DataGrid dataProvider="{ws.getElements.lastResult}" width="100%" height="100%"/>
 
</s:Application>

Then I ran the application in Chrome with the Network view open in the Chrome Developer Tools panel. This indicated that, just as expected, the WSDL is used at runtime:

Next I had a look around the WebService source code (available in <FLEX_SDK>/frameworks/projects/rpc/src/mx/rpc/soap) and discovered there is a loadWSDL method that can be overwritten to handle the embedded loading of the WSDL instead of the default network loading of the WSDL. So I created a new class that extends the base WebService class, embeds the WSDL file, and overrides the loadWSDL method:

package
{
	import mx.core.ByteArrayAsset;
	import mx.core.mx_internal;
	import mx.rpc.events.WSDLLoadEvent;
	import mx.rpc.soap.mxml.WebService;
	import mx.rpc.wsdl.WSDL;
 
	use namespace mx_internal;
 
	public dynamic class MyWebService extends WebService
	{
		[Embed(source="CensusSOAPService.xml", mimeType="application/octet-stream")]
		private static const WSDL_CLASS:Class;
 
		public function MyWebService(destination:String=null)
		{
			super(destination);
			loadWSDL();
		}
 
		override public function loadWSDL(uri:String=null):void
		{
			var thing:Object = new WSDL_CLASS();
			var baa:ByteArrayAsset = (thing as ByteArrayAsset);
			var xml:String = baa.readUTFBytes(baa.length);
 
			deriveHTTPService();
 
			var wsdlLoadEvent:WSDLLoadEvent = WSDLLoadEvent.createEvent(new WSDL(new XML(xml)));
			wsdlHandler(wsdlLoadEvent);
		}
	}
}

In the regular WebService class, setting the wsdl property will trigger the loadWSDL method, but since we are embedding the WSDL, we must manually call the loadWSDL method. I do that in the constructor.

Embedded assets become a class, so first an instance of that class must be instantiated as a ByteArrayAsset (the default type for files embedded with the application/octet-stream mimeType). Then the instance is read into a string. Then the deriveHTTPService method is called to set up the underlying HTTPService‘s channel information. Finally, we create a WSDL object from the XML that was read from the ByteArrayAsset, create a new WSDLLoadEvent with the WSDL, and call the wsdlHandler method, passing it the wsdlLoadEvent. This essentially is doing the same thing as the original WebService class, but now doing it without the network request.

I can now switch my test application to use my extension to WebService instead of the original one:

	<fx:Declarations>
		<local:MyWebService xmlns:local="*" id="ws"/>
	</fx:Declarations>

Now when I run the application and monitor the network activity I no longer see the WSDL being requested at runtime! So everything works when using the WebService API directly. However, the customer I was working with was using the Service wizard in Flash Builder. So we needed to figure out how to get the generated code to use the new WebService extension instead of the original WebService class. I went through the Data Wizards and had it generate the client-side stubs for my Census SOAP Service. This created a CensusSOAPService class that extends the generated _Super_CensusSOAPService class. The CensusSOAPService is intended to give us a place to make modifications to the generated stuff, while the _Super_CensusSOAPService class is not supposed to be modified because it will be overwritten if we refresh the service. Looking in the _Super_CensusSOAPService class I discovered that the WebService instance is being created directly in the constructor:

    public function _Super_CensusSOAPService()
    {
        // initialize service control
        _serviceControl = new mx.rpc.soap.mxml.WebService();
 
        // rest of method omitted
    }

These are the kinds of things that really make you wish the Flex framework used dependency injection because we need to set _serviceControl from the CensusSOAPService class. So we thought… Alright, this is not ideal but we can just copy the contents of the _Super_CensusSOAPService‘s constructor into CensusSOAPService‘s constructor, replace the line that instantiates the WebService, have it instantiate MyWebService instead, and then just not call super(). We gave it a try and for some reason kept getting _serviceControl set as a WebService not MyWebService. WTF? It made no sense until we found this little gem in the Flex docs:
“If you define the constructor, but omit the call to super(), Flex automatically calls super() at the beginning of your constructor.”
Now it all made sense! Since we didn’t call super(), Flex conveniently inserted a super() call for us! Fun. So we had to figure out a way to convince the Flex compiler that we were going to call super(), but then not call it.

if (0)
{
    super();
}

Voila! Now the CensusSOAPService‘s constructor sets _serviceControl to a new instance of MyWebService and _Super_CensusSOAPService doesn’t get the chance to mess that up.

We tested the new CensusSOAPService and everything worked perfectly!

I hope that helps some of you who are using the Flex WebService API. Let me know if you have any questions.

This entry was posted in Flex, SOAP and tagged . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.
  • Dean McKee

    I have an Adobe AIR app with an embedded wsdl file. I just set the wsdl property on the webservice in actionscript to my local wsdl file and it seems to find it and load it fine. Am I missing something? Is this a problem only when dealing with straight flex applications?

    • http://www.jamesward.com James Ward

      That would work for an AIR app, but for a browser app that isn’t an option.

    • MAK

      Dean, Did you mean to embed it from the assets directory of your AIR App or from the file system URI? If you have mentioned a local file system path then that wouldn’t work for a browser app. Shouldn’t it work the same if it worked from the app’s assets dir for both browser and air apps? I cant imagine to bloat my SWF size by embedding WSDL increasing the start-up load time of swf, any other better solutions? This is a great solution if SWF Size is not compromised.

      • MAK

        My Bad. I meant, Did you mean to reference it (URI) from the assets directory of your AIR App or embed it from the file system URI?

      • http://www.jamesward.com James Ward

        When embedding into the SWF, the SWF size will be increased, but it will be compressed. So ultimately this is a better deal than loading it over the network after startup.

        • MAK

          Agreed, Embedding is better than loading it over the network. What I was aiming at was to see if there is an option to download it to the local machine similar to an asset (first time) and loading it from local. Webserver would takes care of downloading a new version if there is an updated wsdl. That way SWF size may not be compromised?

          • http://www.jamesward.com James Ward

            You could load it into a Local Shared Object on the client.

  • http://dobieag.com Mark Doberenz

    Thanks dude, that’s awesome. I’ve got a few apps that I’m going to use that on. I’m tired of it always needing to download the WSDL every time it starts up.

    Life saver!

  • http://www.donglongfei.com dong

    Genius!

  • Vivian richard

    Hi James. I did learn all that I know today about Blazeds from your tutorials and presentations. I used to think that Livecycle is very close to Blazeds and since I know about Blazeds it will be fairly simple for me to learn Livecycle. But in livecycle DS is just a very small part; there are soooo many other things. I just do not know where to start from!!! On top of that there are no good tutorial where all the main features of the LC are clearly explained. In this situation I do not see any one but you who can come up with some Livecycle screencast tutorials. Thanks.

  • Debanjan

    Hey James..great work..had a question though..would this work with Flex 3..i tried using your technique ..but no luck yet.. any thoughts?

  • Hassa

    I have hound your post extremely very helpful …. exactly what I was looking for ….
    I have done the same as you have mentioned …..
    but when I call the service method, it says no such method found ….
    can you please help me out in this ….
    Thanks…

  • Hassan

    I am using a WCF service and its wsdl is not as detailed as yours (or I might be missing some configuration), it does not contain the information regarding the methods parameters, nor objects used.
    Can u help me out here…
    Thanks…

  • Pierangelo

    hi James, this hack doesn’t work if in the embedded WSDL there’s a direct import of an XSD file.
    I think couse the WebService.loadWSDL() method loads the WSDL using a XMLLoader…i’m investigating about it….
    Also you can avoid to set a “fake” super() call, instatiating your extented mxml.WebService Class in the _Super_CensusSOAPService and disabling, in the preinizialize method, the instruction “::loadWSDLifNecessary()”….

    btw thanks for all yours posts around internet, always intersting!
    Pierangelo

    • Dturkel

      Hi Pierangelo — did you find out anything else about this, as I have a complex WSDL (for MS Exchange Web Services), where the only way I can figure out how to use these services is embedding them locally (as I have to add a “wsdl:service” tag to make it compliant, but then it also has import XSD’s…

      • Pierangelo

        Hi Dturkel, the workaroud that i did is to use the flex3builder plugin to import the WS and to generate the valueObjects …also i can say that flash builder4 and 4.5 WS import plugin didn’t generate all classes defined in the XSD….

        p.s: sorry for my late answer, but i didn’t set notification flag :)

        i hope this help  

        • Dturkel

          Interesting — I will have to try that.  Basically I was investing too much
          time in this, and since my transaction set was limited to CreateItem,
          CreateAttachment, and SendItem, I just templated them out as strings, and sent
          them using HTTP (forget the specific classes), rather than going through the
          generated WSDL service classes.  Took about 30mins to get completely working,
          instead of banging my head.

           

          Long term, probably not a great solution — especially if you actually need
          to support all of the services (and combinations of options) for MS Exchange.   

          • Kassim

            Dturkel,

            Can you please help me to connect to MS EWS using AS3

  • Kokorin Denis

    Hello, James!
    At first thank you for the post.

    But I want to propose slightly different way using  CensusSOAPService.preInitializeService() function.

    You forbade call to super constructor. Flash Builder generates not only WebServices stubs but also response types. Using this technic in result handlers of AsyncToken you will get ObjectProxy instead of strongly-typed objects.

    To enable typed objects in result handlers it’s needed to let  _Super_CensusSOAPService to create _serviceControl property, create new CensusSOAPService() and set operations and convertResultHandler.
    Here is the sujested code:

    override protected function preInitializeService():void{
    var newWebService:WebService = new MyWebService();
    var newOperations:Object = new Object();
    for(var opName:String in _serviceControl.operations){
    var operation:Operation = _serviceControl.operations[opName] as Operation;
    var newOperation:Operation = new Operation(null, opName);
    newOperation.resultType = operation.resultType;
    newOperations[opName] = newOperation;
    }
    newServiceControl.operations = newOperations;
    newServiceControl.convertResultHandler = _serviceControl.convertResultHandler;
    _serviceControl = newServiceControl;
    super.preInitializeService();
    }

  • BeMor

    For mobile app I just set the wsdl in preInitializeService to equal “app:///assets/mywsdldef.xml” and seems to work well.

  • girldragon

    Hello James!

    Thank you for the post.  It’s a really nice solution to embed the WSDL.  I would like to propose a simpler way of doing it, though, which will save you from the super constructor hassle.  It is to simply override the very annoying model_internal::loadWSDLIfNecessary in the modifiable generated class itself:

    package services.service
    {
        import com.adobe.fiber.core.model_internal;   
        import mx.core.ByteArrayAsset;
        import mx.core.mx_internal;
        import mx.rpc.events.FaultEvent;
        import mx.rpc.events.WSDLLoadEvent;
        import mx.rpc.soap.SOAPHeader;
        import mx.rpc.soap.WebService;
        import mx.rpc.wsdl.WSDLLoader;
        import mx.rpc.wsdl.WSDL;
        
        use namespace model_internal;

        public class Service extends _Super_Service
        {           
       
            [Embed(source="/services/wsdl/Service.xml", mimeType="application/octet-stream")]
            private static const WSDL_CLASS:Class;   

            private var isWSDLLoaded:Boolean = false;
               
            public function get webService():WebService
            {
                return _serviceControl as WebService;
            }

            override model_internal function loadWSDLIfNecessary():void
            {
                if (!isWSDLLoaded)
                {
                    //load may not be completed but we only want to load once
                    isWSDLLoaded = true;
                    loadWSDLFromAsset();
                }
            }

            public function loadWSDLFromAsset():void
            {
                var wsdlObj:Object = new WSDL_CLASS();
                var baa:ByteArrayAsset = (wsdlObj as ByteArrayAsset);
                var xml:String = baa.readUTFBytes(baa.length);

                webService.mx_internal::deriveHTTPService();

                var wsdlLoadEvent:WSDLLoadEvent = WSDLLoadEvent.createEvent(new WSDL(new XML(xml)));
                webService.mx_internal::wsdlHandler(wsdlLoadEvent);
            }
        }
    }

    I’ve got this working and loading our rather big WSDL – saving lots of bandwidth.

  • Daniel Ruiz

    Hi, 

    I’m currently developing a Mobile App and I’m just doing a this.wsdl = “/PATH” and using the loadWSDLIfNecessary.

    It’s working here, I actually do the load straight away because I need to refresh a bunch of WSDL’s when the user changes the System..

    • Morten Gorm Madsen

      Well the wsdl won’t be embedded this way. That’s part of the reason for the workarounds

  • Kassim

    James Ward,

    Can you please help in consuming MS EWS using AS3

    Regards
    Kasim

  • Nivedita

    I have an Adobe AIR app developed in air sdk 13(latest version) and am calling a webservice to authenticate user. When I run this app in simulator it works fine but on app it throws an error “Unable to load WSDL If online…”. I have used crossdomain as well.. but it won’t help. Am I missing something here?



  • View James Ward's profile on LinkedIn