Создание асинхронного прокси для обращения к 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 для отображения информации, респонзы по которым уже поступили.
<SITEMAP> <MVC> <ASP> <NET> <DATA> <KIOSK> <FLEX> <SQL> <NOTES> <LINUX> <MONO> <FREEWARE> <DOCS> <ENG> <CHAT ME> <ABOUT ME> < THANKS ME> |