Блог программиста Еремина Вячеслава Викторовича
(Flex) Flex (2012 год)

Создание асинхронного прокси для обращения к WCF средствами Adobe flex builder.

У меня на сайте очень подробно описано как обращатся к web-сервисам СИНХРОННО, то есть с подвисанием основного потока Flex-AIR приложения - Как сделать SOAP/WSDL-вебсервис на ASP.NET/MONO для вызова его из FLEX.

А в этой короткой ремарке я бы хотел напомнить об АСИНХРОННОМ обращении к сервисам Windows Communication Foundation.

Кода для асинхронного обращения надо много и он требует глубокого понимания работы виртуальной машины FLASH. К счастью этот код можно создать автоматически всего в пару кликов мышкой.

Итак, у меня есть вот такой WCF-сервис:



Для создания асинхронного обращения к WCF в Adobe Flex Builder достаточно всего лишь научится пользоватся мышкой и три раза не промахнутся мышкой по кнопке Next:



Автоматически были созданы целые горы кода:



Теперь добавим вызовы этого кода в основной код формы:



И проверим как вызывается WCF-сервис:



Что мне нравится в продуктах Adobe - что они построены более грамотно чем (индустское чудо - русское горе) Microsoft Visual Studio. Это чудо в приниципе тоже умеет строить аналогичные прокси (для синхронного обращения), однако прибиндить к web-сервису контрол в один клик мышкой у меня так и не получилось. На платформе Adobe я просто кликнул ОДИН РАЗ (причем у меня раскрылась подсказка куда кликнуть).



Биндинг выглядит в виде одной вполне понятной строчке - (скобки означают итерацию по коллекции).



Итак, запустим софтинку, созданную за пару кликов мышкой и проверим как прибиндился комбобокс:



А вот в Microsoft Visual Studio сколько я не тыкал мышкой - ничего не получилось. Мне пришлось прибиндится к тому же сервису только с помощью кода.



Теперь еще один важный момент. WCF - весьма накрученная индусами технология. Теме различных видов биндингов WCF у меня посвящен отдельный топик - Конфиги WCF-сервисов, обеспечивающие совестимость с JAVA, PHP, FLEX. Но к счастью, в конфигурации по умолчанию создается именно тот самый basicHttpBinding, который и совместим с основными немикрософтовскими платформами:



К слову сказать, работать с асинхронными web-сервисами во FLEX надо совершенно не так, как в NET - NET Framework изначально многопоточная виртуальная машина, прерывающая потоки и сохраняющая их состояние. Поэтому в NET Framework можно получить некий список, а потом просто выдать в цикле серию обращений к web-сервисам. События ответа от сервисов могут прийти в любом порядке, это значения не имеет, накакой CallBack завершения сервиса чужой CallBack не затрет и выданные в цикле реквесты никак не пересекаются.

Во FLEX картина противоположная. Виртуальная машина ФЛЕКС - однопоточная. Она тоже в приниципе может прерыватся (особенно это важно для векторной графики, где без этого эффекта сохранения состояния прерванного потока вообще невозможно рисовать - смотрите топик Модуль векторной графики для построения графов, но компоненты s:CallResponder строго однопоточные - так же как например Windows-формы (из-за чего приходится например делать маршализацию в поток формы). Таким образом просто выдать в цикле серию обращений к <s:CallResponder нельзя, реквесты будут затерты и респонзы будут утеряны.

Таким образом для вот такого определения асинхронных web-сервисов:


   1:      <fx:Declarations>
   2:          <s:CallResponder id="GetGoodTicketsResult"/>
   3:          <s:CallResponder id="GetOneTicketDilerInfoResult"/>
   4:          <ticketlist:TicketList id="ticketList" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
   5:          <s:CallResponder id="GetCursResult"/>
   6:          <cursusd:CursUSD id="cursUSD" fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)" showBusyCursor="true"/>
   7:      </fx:Declarations>

В котором метод GetGoodTicketsResult выдает список GUID-ов, с каждым из которых надо далее обратится к сервису GetOneTicketDilerInfoResult - обращения в цикле выдать нельзя, код может быть только такой:


   1:  protected function WebServiceInit():void{
   ...   
  72:      //ждем завершения операций
  73:      
  74:      GetCursResult.addEventListener(ResultEvent.RESULT,getCursResult_Result);
  75:      GetCursResult.addEventListener(FaultEvent.FAULT,getCursResult_Fault);
  76:          
  77:      GetOneTicketDilerInfoResult.addEventListener(ResultEvent.RESULT,getOneTicketDilerInfo_Result);
  78:      GetOneTicketDilerInfoResult.addEventListener(FaultEvent.FAULT,getOneTicketDilerInfo_Fault);
  79:      
  80:      GetGoodTicketsResult.addEventListener(ResultEvent.RESULT,getGoodTicketsResult_Result);
  81:      GetGoodTicketsResult.addEventListener(FaultEvent.FAULT,getGoodTicketsResult_Fault);
  82:      
  83:      GetLessTicketsResult.addEventListener(ResultEvent.RESULT,getLessTicketsResult_Result);
  84:      GetLessTicketsResult.addEventListener(FaultEvent.FAULT,getLessTicketsResult_Fault);
  85:      
  86:      GetMoreTicketsResult.addEventListener(ResultEvent.RESULT,getMoreTicketsResult_Fault);
  87:      GetMoreTicketsResult.addEventListener(FaultEvent.FAULT,getMoreTicketsResult_Fault);
  88:  }
  89:   
  90:  protected var USDCurs:Number;
  91:  protected function WebServiceStart():void{
  92:      //первый запрос - курс валюты
  93:      getCurs();
  94:  }
  95:   
  96:  protected function getCurs():void
  97:  {
  98:      GetCursResult.token = cursUSD.GetCurs();
  99:  }
 100:   
 101:  protected function getCursResult_Result(e:ResultEvent):void{
 102:      USDCurs=GetCursResult.token.result as Number;
 103:      //второй запрос - список билетов
 104:      Tickets = new ArrayCollection();
 105:      var Request:TicketRequest = new TicketRequest();
 106:      Request.OneWay = !URL_Parm.IsReturn;
 107:      Request.FromCountry =  URL_Parm.IsDirection ? "Россия" : "Турция"; 
 108:      Request.FromCity = URL_Parm.IsDirection ?   "Москва" : "Анталия"; 
 109:      Request.FromDate = URL_Parm.FromDate;
 110:      Request.ToCountry = URL_Parm.IsDirection ? "Турция" : "Россия"  ; 
 111:      Request.ToCity = URL_Parm.IsDirection ?    "Анталия" : "Москва" ; 
 112:      Request.ToDate = URL_Parm.ToDate;
 113:      getGoodTickets(Request);    
 114:  }
 115:   
 116:   
 117:  protected function getOneTicketDilerInfo(TicketID:OneGUID, Login:AU):void
 118:  {
 119:      trace (TicketID.GUID == null ? "getOneTicketDilerInfo" : "getOneTicketDilerInfo: "+TicketID.GUID);
 120:      GetOneTicketDilerInfoResult.token = ticketList.GetOneTicketDilerInfo(TicketID, Login);
 121:  }
 122:   
 123:  //циклический вызов информации о следующем билете после завершения реквеста о текущем
 124:  protected function getOneTicketDilerInfo_Result(e:ResultEvent):void{
 125:      if (e !== null){
 126:          if (GetOneTicketDilerInfoResult.token.result !== null){
 127:              var OneTicketDilerInfo:valueObjects.OneTicket = new valueObjects.OneTicket();
 128:              OneTicketDilerInfo = GetOneTicketDilerInfoResult.token.result as valueObjects.OneTicket;
 129:              for (var j:int=0; j<Tickets.length;j++){
 130:                  if (Tickets[j].DirectTicket !==null && Tickets[j].DirectTicketInfo == null){
 131:                      if (Tickets[j].DirectTicket == OneTicketDilerInfo.ID ){
 132:                          Tickets[j].DirectTicketInfo = OneTicketDilerInfo;
 133:                          trace ("getOneTicketDilerInfo_Result: "+ OneTicketDilerInfo.ID);
 134:                          break;
 135:                      }
 136:                  }
 137:                  if (Tickets[j].ReturnTicket !==null && Tickets[j].ReturnTicketInfo == null){
 138:                      if (Tickets[j].ReturnTicket == OneTicketDilerInfo.ID ){
 139:                          Tickets[j].ReturnTicketInfo = OneTicketDilerInfo;
 140:                          trace ("getOneTicketDilerInfo_Result: "+OneTicketDilerInfo.ID);
 141:                          break;
 142:                      }
 143:                  }
 144:              }
 145:          }
 146:      }
 147:     //выдаем очередной реквест
 148:      for (var i:int=0; i<Tickets.length;i++){
 149:          if (( Tickets[i].DirectTicket !== null && Tickets[i].DirectTicketInfo == null ) ||( Tickets[i].ReturnTicket !== null && Tickets[i].ReturnTicketInfo == null )){
 150:              //остались незавершенные запросы к сервисам
 151:              var OneGuid1:valueObjects.OneGUID = new OneGUID;
 152:              if (Tickets[i].DirectTicket !== null && Tickets[i].DirectTicketInfo == null ){
 153:                  OneGuid1.GUID = Tickets[i].DirectTicket;
 154:                  getOneTicketDilerInfo(OneGuid1,Login);
 155:                  return;
 156:              }
 157:              if (Tickets[i].ReturnTicket !== null && Tickets[i].ReturnTicketInfo == null ){
 158:                  OneGuid1.GUID = Tickets[i].ReturnTicket;
 159:                  getOneTicketDilerInfo(OneGuid1,Login);
 160:                  return;
 161:              }
 162:          }
 163:      }
 164:      //все завершено
 165:      WebServiceEnd();
 166:  }    
 167:   
 168:   
 169:  protected function getGoodTicketsResult_Result(e:ResultEvent):void{
 170:      var List1:ArrayCollection = GetGoodTicketsResult.token.result as ArrayCollection;
 171:      if (List1 !== null){
 172:          for (var i:int=0; i<List1.length; i++){
 173:              //Tickets.addItem([{TicketType.Good}, (List1[i] as TicketWithReturn).DirectTicket, (List1[i] as TicketWithReturn).ReturnTicket]);
 174:              Tickets.addItem({
 175:                  "TicketType":TicketType.Good, 
 176:                  "USDCurs":USDCurs,
 177:                  "DirectTicket":(List1[i] as TicketWithReturn).DirectTicket, 
 178:                  "ReturnTicket":(List1[i] as TicketWithReturn).ReturnTicket,
 179:                  "DirectTicketInfo":null,
 180:                  "ReturnTicketInfo":null
 181:              });
 182:          }
 183:          //второй запрос - список параметров каждого билета
 184:          getOneTicketDilerInfo_Result(null);    
 185:      }
 186:      else{
 187:          //или скормить репитеру пустую коллекцию (погасить отображение билетов)
 188:          WebServiceEnd();
 189:      }
 190:  }

Как вы видите - я сначала выдал GetCurs (получил одиночный результат с курсом валюты), потом получил список GUID методом getGoodTickets. Зафиксировал каждый необходимый последующий реквест в коллекции Tickets и не просто выдал все подряд обращения (как бы я сделал в NET Framework) - а аккуратно выдаю каждый новый реквест после завершения предыдущего.

В это время мне ничто не мешает использовать частично заполненный Tickets для отображения информации, респонзы по которым уже поступили.



Комментарии к этой страничке ( )
ссылка на эту страничку: http://www.vb-net.ru/FlexAsynToWcf/index.htm
<На главную>  <В раздел ASP>  <В раздел NET>  <В раздел SQL>  <В раздел Разное>  <Написать автору>  < Поблагодарить>