Flex e web services: scenari e soluzioni

Immagini

Immagine 1 Immagine 2 Immagine 3

Com’è noto Flex dispone di uno specifico componente che è possibile usare per le comunicazioni con i web services. In questo articolo prenderemo in esame alcune soluzioni che potremo convenientemente adoperare in diversi casi.

Realizzeremo tre semplici web services di esempio in C# e tre applicazioni client in Flex che presenteranno modalità implementative differenti a seconda della complessità degli scenari.

Cominciamo con un primo caso, semplice ma molto comune: un WebMethod che restituisce un elenco (nell’esempio i sedici colori web standard) pubblicato tramite una DataGrid Flex.

  1. <%@ WebService Language=”C#” Class=”MyWebService” %>
  2. using System;
  3. using System.Web.Services;
  4. using System.Xml.Serialization;
  5. using System.Collections.Generic;
  6. [WebService(Namespace=”http://abc.com/webservices/”)]
  7. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  8. public class MyWebService : System.Web.Services.WebService {
  9. public MyWebService() { }
  10. [WebMethod]
  11. [XmlInclude(typeof(Color))]
  12. public List<Color> getColors() {
  13. List<Color> l = new List<Color>();
  14. l.Add(new Color(“Aqua”, “#00FFFF”));
  15. l.Add(new Color(“Black”, “#000000”));
  16. l.Add(new Color(“Blue”, “#0000FF”));
  17. l.Add(new Color(“Fuchsia”, “#FF00FF”));
  18. l.Add(new Color(“Gray”, “#808080”));
  19. l.Add(new Color(“Green”, “#008000”));
  20. l.Add(new Color(“Lime”, “#00FF00”));
  21. l.Add(new Color(“Maroon”, “#800000”));
  22. l.Add(new Color(“Navy”, “#000080”));
  23. l.Add(new Color(“Olive”, “#808000”));
  24. l.Add(new Color(“Purple”, “#800080”));
  25. l.Add(new Color(“Red”, “#FF0000”));
  26. l.Add(new Color(“Silver”, “#C0C0C0”));
  27. l.Add(new Color(“Teal”, “#008080”));
  28. l.Add(new Color(“White”, “#FFFFFF”));
  29. l.Add(new Color(“Yellow”, “#FFFF00”));
  30. return l;
  31. }
  32. }
  33. [Serializable] public class Color {
  34. public String name;
  35. public String hex;
  36. public String rgb;
  37. public Color() { }
  38. public Color(String n, String h) {
  39. name = n;
  40. hex = h;
  41. rgb = hexToRgb(hex);
  42. }
  43. private String hexToRgb(String h) {
  44. int r = (System.Int32.Parse(h.Substring(1, 2),
  45. System.Globalization.NumberStyles.AllowHexSpecifier));
  46. int g = (System.Int32.Parse(h.Substring(3, 2),
  47. System.Globalization.NumberStyles.AllowHexSpecifier));
  48. int b = (System.Int32.Parse(h.Substring(5, 2),
  49. System.Globalization.NumberStyles.AllowHexSpecifier));
  50. return r.ToString() + “,” + g.ToString() + “,” + b.ToString();
  51. }
  52. }

Nel web service troviamo una classe Color con tre campi pubblici che conterranno nome, valore esadecimale e rgb (tre numeri separati da virgola: la sintassi adoperata nei CSS) dei diversi colori. Quest’ultimo valore è ricavato da quello esadecimale con il metodo privato hexToRgb. Tutte le istanze valorizzate di questa classe vengono raccolte in una collection List nel WebMethod getColors che, ad ogni chiamata, restituisce proprio questa collection.
Salviamo il file come “MyWebService.asmx”, configuriamo IIS per la pubblicazione e invochiamo getColors da un browser qualsiasi.
Otterremo:

  1. <ArrayOfColor>
  2. <Color>
  3. <name>Aqua</name>
  4. <hex>#00FFFF</hex>
  5. <rgb>0,255,255</rgb>
  6. </Color>
  7. ...
  8. </ArrayOfColor>

Il frammento presenta la serializzazione della collection come ArrayOfColor: il nodo root dispone tutte le istanze di Color con i relativi campi pubblici e i valori ivi contenuti come nodi elemento e nodi testo. Realizziamo ora un client in Flex per consumare il web service. L’applicazione è semplicissima:

  1. <?xml version=”1.0” encoding=”iso-8859-1”?>
  2. <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”>
  3. <mx:WebService
  4. id=”ws”
  5. useProxy=”false”
  6. wsdl=”http://localhost/Colors/MyWebService.asmx?wsdl”>
  7. <mx:operation name=”getColors” />
  8. </mx:WebService>
  9. <mx:Panel width=”350” height=”500”>
  10. <mx:DataGrid
  11. width=”100%”
  12. height=”100%”
  13. dataProvider=”{ws.getColors.lastResult}”>
  14. <mx:columns>
  15. <mx:DataGridColumn dataField=”name” headerText=”Name” />
  16. <mx:DataGridColumn dataField=”hex” headerText=”Hex” />
  17. <mx:DataGridColumn dataField=”rgb” headerText=”Rgb” />
  18. </mx:columns>
  19. </mx:DataGrid>
  20. <mx:ControlBar>
  21. <mx:Spacer width=”100%” />
  22. <mx:Button label=”Test” click=”ws.getColors.send()” />
  23. </mx:ControlBar>
  24. </mx:Panel>
  25. </mx:Application>

Abbiamo valorizzato la proprietà wsdl con l’url della descrizione del servizio, assegnato il nome del WebMethod all’elemento operation, definita la chiamata di getColors (evento click del pulsante “Test”) e, infine, passato l’esito dell’invocazione (l’ArrayOfColor convertito in ArrayCollection) come dataProvider della DataGrid.
I nomi assegnati alle proprietà dataField degli elementi DataGridColumn sono naturalmente gli stessi dei campi pubblici della classe Color definita nel web service. Compiliamo l’applicazione e carichiamola da un browser: un click sul tasto “Test” popolerà la tabella con i nomi e i valori dei sedici colori. (Immagine 1)

Passiamo adesso ad un caso leggermente più complesso: un WebMethod che restituisce l’elenco delle regioni e delle relative province italiane. La soluzione non presenta particolari differenze nel web service (solo una classe in più e ile il codice per leggere un XML contenente i nomi di regioni e province)

  1. <%@ WebService Language=”C#” Class=”MyWebService” %>
  2. using System;
  3. using System.Xml;
  4. using System.Web.Services;
  5. using System.Xml.Serialization;
  6. using System.Collections.Generic;
  7. [WebService(Namespace=”http://abc.com/webservices/”)]
  8. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  9. public class MyWebService : System.Web.Services.WebService {
  10. public MyWebService() { }
  11. [WebMethod]
  12. [XmlInclude(typeof(Region))]
  13. public List<Region> getRegions() {
  14. XmlDocument xd = new XmlDocument();
  15. xd.Load(Server.MapPath(“italy.xml”));
  16. List<Region> r = new List<Region>();
  17. foreach (XmlNode rn in xd.DocumentElement.ChildNodes) {
  18. List<Province> p = new List<Province>();
  19. foreach (XmlNode pn in rn.ChildNodes) {
  20. p.Add(new Province(pn.Attributes.Item(0).Value));
  21. }
  22. r.Add(new Region(rn.Attributes.Item(0).Value, p));
  23. }
  24. return r;
  25. }
  26. }
  27. [Serializable] public class Region {
  28. [XmlAttribute(“name”)]
  29. public String name;
  30. public List<Province> Provinces;
  31. public Region() { }
  32. public Region(String n, List<Province> p) {
  33. name = n;
  34. Provinces = p;
  35. }
  36. }
  37. [Serializable] public class Province {
  38. [XmlAttribute(“name”)]
  39. public String name;
  40. public Province() { }
  41. public Province(String n) {
  42. name = n;
  43. }
  44. }

Di seguito un frammento del documento prodotto dal metodo getRegions

  1. <ArrayOfRegion>
  2. <Region name=”Abruzzo”>
  3. <Provinces>
  4. <Province name=”L’Aquila” />
  5. <Province name=”Chieti” />
  6. <Province name=”Pescara” />
  7. <Province name=”Teramo” />
  8. </Provinces>
  9. </Region>
  10. <Region name=”Basilicata”>
  11. <Provinces>
  12. <Province name=”Matera” />
  13. <Province name=”Potenza” />
  14. </Provinces>
  15. </Region>
  16. ...
  17. </ArrayOfRegion>

L’architettura dell’applicazione client, invece, presenta notevoli differenze. In particolari circostanze può essere più conveniente centralizzare la comunicazione con il web service mediante uno specifico oggetto (Remote Proxy, vedi http://en.wikipedia.org/wiki/Proxy_pattern). In questo modo tutti i client del progetto accederanno alle informazioni pubblicate dal web service utilizzando sempre lo stesso oggetto

  1. package {
  2. import mx.rpc.soap.WebService;
  3. import mx.rpc.events.ResultEvent;
  4. import mx.collections.ArrayCollection;
  5. import RegionsModel;
  6. public class RegionsProxy {
  7. private var ws:WebService;
  8. public function RegionsProxy() {
  9. ws = new WebService();
  10. ws.wsdl = “http://localhost/Regions/MyWebService.asmx?wsdl”;
  11. ws.loadWSDL();
  12. }
  13. public function getRegions():void {
  14. ws.getRegions.addEventListener(ResultEvent.RESULT, getRegionsResultHandler);
  15. ws.getRegions();
  16. }
  17. private function getRegionsResultHandler(e:ResultEvent):void {
  18. ws.getRegions.removeEventListener(ResultEvent.RESULT, getRegionsResultHandler);
  19. RegionsModel.getInstance().regions = ws.getRegions.lastResult as ArrayCollection;
  20. }
  21. }
  22. }

Il gestore dell’evento result, come si vede, passa come ArrayCollection l’esito dell’invocazione del WebMethod ad una proprietà di un altro oggetto: RegionModel. Quest’oggetto è un Data Model realizzato con un Singleton (una delle implementazioni più semplici, vedi http://en.wikipedia.org/wiki/Singleton_pattern) e qui adoperato come “ponte” tra il proxy ed il fruitore (o i fruitori) dei dati

  1. package {
  2. import mx.collections.ArrayCollection;
  3. public class RegionsModel {
  4. private static var instance:RegionsModel;
  5. private var r:ArrayCollection;
  6. [Bindable]
  7. public function set regions(v:ArrayCollection):void {
  8. r = v;
  9. }
  10. public function get regions():ArrayCollection {
  11. return r;
  12. }
  13. public function RegionsModel() {}
  14. public static function getInstance():RegionsModel {
  15. if (instance == null) {
  16. instance = new RegionsModel();
  17. }
  18. return instance;
  19. }
  20. }
  21. }

Un banale esempio di client per testare il progetto

  1. <?xml version=”1.0” encoding=”iso-8859-1”?>
  2. <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”>
  3. <mx:Script>
  4. <![CDATA[
  5. import RegionsModel;
  6. import RegionsProxy;
  7. private function bceh():void {
  8. var rp:RegionsProxy = new RegionsProxy();
  9. rp.getRegions();
  10. }
  11. ]]>
  12. </mx:Script>
  13. <mx:Panel
  14. paddingTop=”10”
  15. paddingRight=”10”
  16. paddingBottom=”10”
  17. paddingLeft=”10”>
  18. <mx:ComboBox
  19. width=”200”
  20. id=”region”
  21. dataProvider=”{RegionsModel.getInstance().regions}”
  22. labelField=”name” />
  23. <mx:ComboBox width=”200”
  24. dataProvider=”{region.selectedItem.Provinces}”
  25. labelField=”name” />
  26. <mx:ControlBar>
  27. <mx:Spacer width=”100%” />
  28. <mx:Button label=”Test” click=”bceh()” />
  29. </mx:ControlBar>
  30. </mx:Panel>
  31. </mx:Application>

L’esito dell’invocazione viene passato dalla proprietà regions del model al primo ComboBox (che elenca le regioni) come dataProvider. Il secondo ComboBox (che elenca le province) ha come dataProvider l’array Provinces restituito dall’elemento selezionato del primo. (Immagine 2)

Chiudiamo con un ultimo caso: un WebMethod che restituisce la serializzazione di un oggetto strutturato in modo leggermente diverso rispetto a quelli finora visti che, lato consumer, verrà prima mappato ad una corrispondente classe AS3 e poi fruito nelle consuete modalità dal (o dai) client.

  1. <%@ WebService Language=”C#” Class=”MyWebService” %>
  2. using System;
  3. using System.Web.Services;
  4. using System.Xml.Serialization;
  5. using System.Collections.Generic;
  6. [WebService(Namespace=”http://abc.com/webservices/”)]
  7. [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  8. public class MyWebService : System.Web.Services.WebService {
  9. public MyWebService() { }
  10. [WebMethod]
  11. [XmlInclude(typeof(Student))]
  12. public Student getStudent() {
  13. Student s = new Student(1, “Giuseppe”, “Rossi”);
  14. return s;
  15. }
  16. }
  17. [Serializable] public class Student {
  18. [XmlAttribute(“id”)]
  19. public int id;
  20. public String name;
  21. public String surname;
  22. public List<Exam> Exams;
  23. public Student() { }
  24. public Student(int i, String n, String s) {
  25. id = i;
  26. name = n;
  27. surname = s;
  28. Exams = new List<Exam>();
  29. Exams.Add(new Exam(“Algoritmi e strutture dati”, “25/30”));
  30. Exams.Add(new Exam(“Ingegneria del software”, “30/30”));
  31. Exams.Add(new Exam(“Sistemi operativi”, “26/30”));
  32. }
  33. }
  34. [Serializable] public class Exam {
  35. public String name;
  36. public String grade;
  37. public Exam() { }
  38. public Exam(String n, String g) {
  39. name = n;
  40. grade = g;
  41. }
  42. }

Questo il documento XML restituito da getStudent:

  1. <Student>
  2. <name>Giuseppe</name>
  3. <surname>Rossi</surname>
  4. <Exams>
  5. <Exam>
  6. <name>Algoritmi e strutture dati</name>
  7. <grade>25/30</grade>
  8. </Exam>
  9. <Exam>
  10. <name>Ingegneria del software</name>
  11. <grade>30/30</grade>
  12. </Exam>
  13. <Exam>
  14. <name>Sistemi operativi</name>
  15. <grade>26/30</grade>
  16. </Exam>
  17. </Exams>
  18. </Student>

Il codice del Remote Proxy è sostanzialmente identico a quello già visto sopra ma stavolta alla proprietà del Data Model non viene assegnato semplicemente l’esito dell’invocazione del WebMethod ma una istanza della classe Student

  1. package {
  2. import mx.rpc.soap.WebService;
  3. import mx.rpc.events.ResultEvent;
  4. import StudentModel;
  5. import Student;
  6. public class StudentProxy {
  7. private var ws:WebService;
  8. public function StudentProxy() {
  9. ws = new WebService();
  10. ws.wsdl = “http://localhost/Student_FX2/MyWebService.asmx?wsdl”;
  11. ws.loadWSDL();
  12. }
  13. public function getStudent():void {
  14. ws.getStudent.addEventListener(ResultEvent.RESULT, getStudentResultHandler);
  15. ws.getStudent();
  16. }
  17. private function getStudentResultHandler(e:ResultEvent):void {
  18. ws.getStudent.removeEventListener(ResultEvent.RESULT, getStudentResultHandler);
  19. StudentModel.getInstance().student = new Student(ws.getStudent.lastResult);
  20. }
  21. }
  22. }

Con la classe Student praticamente ricostruiamo l’oggetto serializzato dal web service

  1. package {
  2. import mx.collections.ArrayCollection;
  3. public class Student {
  4. private var i:int;
  5. private var n:String;
  6. private var s:String;
  7. private var e:ArrayCollection;
  8. [Bindable]
  9. public function set id(v:int):void {
  10. i = v;
  11. }
  12. public function get id():int {
  13. return i;
  14. }
  15. [Bindable]
  16. public function set name(v:String):void {
  17. n = v;
  18. }
  19. public function get name():String {
  20. return n;
  21. }
  22. [Bindable]
  23. public function set surname(v:String):void {
  24. s = v;
  25. }
  26. public function get surname():String {
  27. return s;
  28. }
  29. [Bindable]
  30. public function set exams(v:ArrayCollection):void {
  31. e = v;
  32. }
  33. public function get exams():ArrayCollection {
  34. return e;
  35. }
  36. public function Student(o:Object) {
  37. i = o.id;
  38. n = o.name;
  39. s = o.surname;
  40. e = o.exams as ArrayCollection;
  41. }
  42. }
  43. }

La ricostruzione di Student avviene nel costruttore della classe che riassegna i valori dei campi uno ad uno. Questa procedura è indispensabile con Flex 2 che non è in grado di mappare in automatico un tipo custom restituito da un web service ad una classe AS3. Tale limitazione però è stata superata, come vedremo, in Flex 3.

Ometto il codice del Data Model perché differisce dal precedente solo nel tipo restituito dalla proprietà: non più ArrayCollection ma Student… Tutto il codice delle soluzioni, comunque, è allegato all’articolo.

Un client d’esempio per testare il caso in oggetto

  1. <?xml version=”1.0” encoding=”iso-8859-1”?>
  2. <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml”>
  3. <mx:Script>
  4. <![CDATA[
  5. import StudentModel;
  6. import StudentProxy;
  7. private function bceh():void {
  8. var cp:StudentProxy = new StudentProxy();
  9. cp.getStudent();
  10. }
  11. ]]>
  12. </mx:Script>
  13. <mx:Panel width=”350” height=”350”>
  14. <mx:Form width=”100%”>
  15. <mx:FormItem label=”ID”>
  16. <mx:TextInput
  17. width=”200”
  18. text=”{StudentModel.getInstance().student.id.toString()}” />
  19. </mx:FormItem>
  20. <mx:FormItem label=”Nome”>
  21. <mx:TextInput
  22. width=”200”
  23. text=”{StudentModel.getInstance().student.name}” />
  24. </mx:FormItem>
  25. <mx:FormItem label=”Cognome”>
  26. <mx:TextInput
  27. width=”200”
  28. text=”{StudentModel.getInstance().student.surname}” />
  29. </mx:FormItem>
  30. </mx:Form>
  31. <mx:DataGrid
  32. width=”100%”
  33. height=”100%”
  34. dataProvider=”{StudentModel.getInstance().student.exams}”>
  35. <mx:columns>
  36. <mx:DataGridColumn dataField=”name” headerText=”Esame”/>
  37. <mx:DataGridColumn dataField=”grade” headerText=”Voto”/>
  38. </mx:columns>
  39. </mx:DataGrid>
  40. <mx:ControlBar>
  41. <mx:Spacer width=”100%” />
  42. <mx:Button label=”Test” click=”bceh()” />
  43. </mx:ControlBar>
  44. </mx:Panel>
  45. </mx:Application>

(Immagine 3)

Presentiamo, infine, una delle tante novità di Flex 3: la classe SchemaTypeRegistry. Un metodo di questa classe, registerClass, ci consente di ovviare alla carenza vista in precedenza. Di seguito la nuova classe StudentProxy

  1. package {
  2. import mx.rpc.soap.WebService;
  3. import mx.rpc.xml.SchemaTypeRegistry;
  4. import mx.rpc.events.ResultEvent;
  5. import StudentModel;
  6. import Student;
  7. public class StudentProxy {
  8. private var ws:WebService;
  9. private var qn:QName = new QName(“http://abc.com/webservices/”, “Student”);
  10. public function StudentProxy() {
  11. ws = new WebService();
  12. ws.wsdl = “http://localhost/Student_FX3/MyWebService.asmx?wsdl”;
  13. ws.loadWSDL();
  14. SchemaTypeRegistry.getInstance().registerClass(qn, Student);
  15. }
  16. public function getStudent():void {
  17. ws.getStudent.addEventListener(ResultEvent.RESULT, getStudentResultHandler);
  18. ws.getStudent();
  19. }
  20. private function getStudentResultHandler(e:ResultEvent):void {
  21. ws.getStudent.removeEventListener(ResultEvent.RESULT, getStudentResultHandler);
  22. StudentModel.getInstance().student = ws.getStudent.lastResult;
  23. }
  24. }
  25. }

Anche questa classe è un Singleton, quindi, per accedere alle sue proprietà e ai suoi metodi adoperiamo la sintassi SchemaTypeRegistry.getInstance().
Due sono gli argomenti del metodo registerClass: il QName (il Namespace impostato nel web service) e il nome della classe. Il gestore dell’evento result, quindi, passa l’esito dell’invocazione del WebMethod direttamente alla proprietà student del Data Model che viene riconosciuto come tipo di oggetto Student. Il costruttore di questa classe, pertanto, non necessita più delle istruzioni di riassegnazione e può essere svuotato

  1. public function Student() {}

Negli altri file del progetto non cambia nulla. Per testare il tutto compilate di nuovo l’applicazione precedente con il nuovo MXMLC di Flex 3. Al lettore il compito di sperimentare variazioni e, senz’altro, più interessanti applicazioni dei suggerimenti qui presentati.

Scarica i file di progetto http://www.laino.it/usermatter/webservices.zip