Friday, December 26, 2008

Integrating Cairngorm with FB3 Data Wizard Auto generated code.

Flex Builder 3 introduces an agile approach for integrating Flex clients with Web Services, the Web Service Introspection Wizard (WSIW). The wizard lets you specify a service WSDL and auto generates the AS stubs for the service. Zee Yang wrote a great tutorial about this subject demonstrating this tool. Looking at his example, you can understand how simple consuming WS with Flex has become.
The wizard creates a strongly typed stub for each service, and each operation is called by using a specific method. once the call is back you need to listen to a specific event in order to tell whether the call result was successful or not.
As you can see, if you are developing large enterprise SOA applications like I do, you have to write lots of code in order to cover each service.
In my daily work I use Flex WS enabled clients, I also use Cairngorm as a framework for my code. Following are several of the benefits of using Caringrom together with the WSIW:
  1. You don't need to repeat your code - for instance, you can use a single error handler for all ws operations.
  2. Your code is layered and ordered, for each tasks there's a certain type of class operating on different code layer - One example can be the commands that handle the application flow.
  3. Your code is modular - in the future you may replace SOAP WS with a different integration layer (like REST), using Caringrom this task is simple.
To demonstrate the benefits of this methodology, I included an example. Click on the following link here to find the source code. The sample project was kept short and simple for your convenience, I left out many things from the source, for instance, in my daily work I use delegates in order to encapsulate service calls handling. Also note that this specific sample can only be uses with Flash Player 9.
The service I'm using is The Global Weather Service, it lets you get the weather in different cities all over the world. The service has 2 operations, one lets you get a list of cities the service covers fr a given country, the other retrieves the weather forecast for a given city.
1. Import the WSDL
The first thing you need to do in order to consume the WS is to generate local stubs from it's WSDL.
Go to Data > Import Web Service (WSDL) > choose the src folder of your project and press next > Type the WSDL you re about to import (in my case it's and press "finish"
2. Register the service
The local stubs were generated in a default location. Take a look at the BaseGlobalWeather class, in this sample this is the only class that is referenced. The next step would be to make the class available via the service locater.

create a business\Service.mxml document and use the ServiceLocator in order to register the new imported service:

xml version="1.0" encoding="utf-8"?>

 <!-- declare the global weather service, provide the base service class -->

 <globalWeatherService:BaseGlobalWeather id="globalWeatherService"/>


The globalWeatherService will now be available via the ServiceLocator.

3. Create the commands
For each WS operation you will need to create a separate command, use a Base command as the base class for the commands. The tasks the base command needs to perform are:
  1. Declare and set the service and model.
  2. Create global onResult and onFault handlers for the service operations
Here are a few lines from the BaseGloblWeatherCommand:

1 package sample.command
2 {
14     public class BaseGlobalWeatherCommand implements Responder, Command
15     {
16         protected var service:BaseGlobalWeather
17         protected var model:GlobalWeatherModelLocator;
19         public function BaseGlobalWeatherCommand()
20         {
21             //get the ws instance
22             service = BaseGlobalWeather(ServiceLocator.getInstance()
23                 .getService("globalWeatherService"));
24             model = GlobalWeatherModelLocator.getInstance();
25         }
33         /**
34          * Generic on result, all results eventualy goes here
35          */
36         public function onResult(event:*=null):void
37         {
38             //the service returned a response, update the model
39             model.workflowstate = -1;
40         }
42         /**
43          * Generic fault handler all fault responses goes here
44          */
45         public function onFault(event:*=null):void
46         {
47             //the service returned a fault response, update the model and wanr the user
48             model.workflowstate = -1;
49   "An error has accourd:"+event.toString());
50         }
51     }
52 }

Next, write a command per each WS operation, here are a number of lines from the GetCitiesByCountryCommand:

  1 package sample.command
2 {
10     public class GetCitiesByCountryCommand extends BaseGlobalWeatherCommand
11     {
12         public override function execute(event:CairngormEvent):void
13         {
14             var e:GetCitiesByCountryEvent = GetCitiesByCountryEvent(event);
15             var token:AsyncToken = service.getCitiesByCountry(e.countryName);
16             token.addEventListener("result",onResult);
17             token.addEventListener("fault",onFault);
18             model.workflowstate = GlobalWeatherModelLocator.WAITING_WAITING_FOR_SERVICE_RESPONSE;
19         }
21         public override function onResult(event:*=null):void
22         {
23             //handle specific command taks here
24             model.cities = event.result;
25             super.onResult(event);
26         }
27     }
28 }

Notice how my onResult method handles the results in the context of the specific command (line 24), it sets the result of the operation to the relevant member of the model (cities) it then moves on and let the base class handle the results globally, the base class will notify the rest of the application that the application state has changed(line 39 of BaseGloblWeatherCommand):

38             //the service returned a response, update the model
39             model.workflowstate = -1;39             model.workflowstate = -1;

As you can see, by using the commands and the WSIW, you can write applications that talk to web services and maintain a simple and efficient architecture.


Filipino Monkey said...

Very nice.

Thank you.

Drazz said...

Hi there,

Thanks so much for this article , but i have a little question for all of you ppl
Have you ever experienced problems applying this?.
I have problems with ambigous references between Cairngorm and the Flex Framework in mx.rpc classes like
WSDLOperation and others

Lior said...

Hi Drazz, I don't recall having such problems. Is this happening with the code from this post or is it happening with a different project? feel free to link or post you code here so I can take a look.

ashfak said...

explain how data can be retrieve from webservice and store it in datagrid using cairngorm......

ashfak said...

i am getting an error type in webservice project using cairngorm
plz explain

[Data binding will not be able to detect assignments to "DischargeArray".]

Lior Boord said...

@ashfak, sorry for not replying to your earlier question, I guess you already figured that out by yourself.

Regarding the message you now get, this looks like a warning, not an error message. Flex is simply telling you it will not detect binding, it's most likely that you are missing the [Bindable] annotation above the declaration for "DischargeArray"

public var DischargeArray:Array

ashfak said...

Thanks a lot Lior,
Earlierone i got it.

ashfak said...

hi Lior another one question
how to use if and else condition in dropdown list to choose one item and get data from webservice related to that

reggolb said...

Great article.
Have you an update for Flash Builder 4.5.1 Data Service Wizard - or a link to doucumentation?
The generated code is quite different from FB3 Data Wizard.

Lior Boord said...

@reggolb thanks, I did not switch to 4.5.1 yet but once I get a chance I might write an update to this tutorial. I'm happy to hear this feature was updated, there were many issues with previous versions.