• Microsoft .NET
  • WPF и Silverlight
  • Синхронные вызовы веб-службы в Silverlight: Развенчание мифа об исключительной асинхронности

Создание бизнес-приложений с помощью Silverlight - Вызов служб

ОГЛАВЛЕНИЕ

Вызов служб

После того, как реализована вызываемая из Silverlight служба, наступает момент создания прокси служб и их использования для установления связи пользовательского интерфейса с реализациями серверных служб. Надежная генерация прокси для служб WCF возможна только с помощью последовательного выбора пунктов меню Service References | Add Service Reference («Ссылки на службу» | «Добавление ссылки на службу») в Visual Studio. Прокси в моем демонстрационном примере были сгенерированы в пространство имен CallBusinessProxy. Silverlight допускает только асинхронные вызовы сетевых ресурсов, и вызов службы не является исключением. Когда от клиента поступает вызов, клиент Silverlight прослушивает уведомление и отображает диалоговое окно «Принять/Отклонить».

После того, как вызов принят агентом, следующий шаг процедуры заключается в вызове веб-службы для получения сценария агента, соответствующего ситуации данного вызова. Для данного демонстрационного примера я буду использовать только один сценарий, отображенный на рис. 3. В отображенный сценарий входит приветствие и список вопросов, относящихся к безопасности. Агент гарантирует, что прежде, чем предоставлять поддержку, будет получен необходимый минимум ответов на вопросы.

Сценарий агента извлекается посредством получения доступа к методу ICallService.GetAgentScript(), которому в качестве входных данных передается номер заказа. В соответствии с асинхронной моделью программирования, обусловленной стеком веб-служб Silverlight, метод GetAgentScript() доступен в виде CallServiceClient.BeginGetAgentScript(). Во время вызова службы вам необходимо предоставить обработчик обратного вызова, GetAgentScriptCallback, как показано на рис. 4.

Рис. 4 Вызов службы и изменение пользовательского интерфейса Silverlight

class Page:UserControl
{  
  ... 
  void _notifyCallPopup_OnAccept(object sender, EventArgs e)
  {
  AcceptMessage acceptMsg = new AcceptMessage();
  acceptMsg.RepNumber = ClientGlobals.currentUser.RepNumber;
  ClientGlobals.socketClient.SendAsync(acceptMsg);
  this.borderCallProgressView.DataContext = ClientGlobals.callInfo;
  ICallService callService = new CallServiceClient();
  IAsyncResult result = 
  callService.BeginGetAgentScript(ClientGlobals.callInfo.OrderNumber, 
  GetAgentScriptCallback, callService);
  //do a preemptive download of user control
  ThreadPool.QueueUserWorkItem(ExecuteControlDownload);
  //do a preemptive download of the order information
  ThreadPool.QueueUserWorkItem(ExecuteGetOrderDetails, 
  ClientGlobals.callInfo.OrderNumber);
  }

  void GetAgentScriptCallback(IAsyncResult asyncReseult)
  {

  ICallService callService = asyncReseult.AsyncState as ICallService;
  CallBusinessProxy.AgentScript svcOutputAgentScript = 
  callService.EndGetAgentScript(asyncReseult);
  ClientEntityTranslator astobas =  
  SvcScriptToClientScript.entityTranslator;
  ClientEntities.AgentScript currentAgentScript =  
  astobas.ToClientEntity(svcOutputAgentScript)
  as ClientEntities.AgentScript;
  Interlocked.Exchange<ClientEntities.AgentScript>(ref 
  ClientGlobals.currentAgentScript, currentAgentScript);
  if (this.Dispatcher.CheckAccess())
  {
  this.borderAgentScript.DataContext = ClientGlobals.agentScript;
  ... 
  this.hlVerifyContinue.Visibility = Visibility.Visible;
  }
  else
  {
  this.Dispatcher.BeginInvoke(
  delegate()
  {
  this.borderAgentScript.DataContext = ClientGlobals.agentScript;
  ...
  this.hlVerifyContinue.Visibility = Visibility.Visible;

  } );
  }
  }
  private void ExecuteControlDownload(object state)
  {
  WebClient webClient = new WebClient();
  webClient.OpenReadCompleted += new  
  OpenReadCompletedEventHandler(OrderDetailControlDownloadCallback);
  webClient.OpenReadAsync(new Uri("/ClientBin/AdvOrderClientControls.dll", 
  UriKind.Relative));
  }
  ... 
}

Поскольку результат вызова службы можно извлечь только из обработчика обратного вызова, любые изменения состояния приложения Silverlight должны происходить в обработчике обратного вызова.

CallServiceClient.BeginGetAgentScript() вызывается из _notifyCallPopup_OnAccept, работающего в потоке пользовательского интерфейса, и ставит в очередь асинхронный запрос, после чего незамедлительно возвращается к следующему оператору. Поскольку агент сценария еще не доступен, придется подождать, пока не будет запущен обратный вызов, прежде чем кэшировать сценарий и привязывать его к пользовательскому интерфейсу посредством данных.

Успешное завершение вызова службы запускает GetAgentScriptCallback, который получает сценарий, заполняет глобальные переменные и корректирует пользовательский интерфейс, осуществляя привязку сценария агента к соответствующим элементам интерфейса посредством данных. Корректируя пользовательский интерфейс, GetAgentScriptCallback обеспечивает его обновление в потоке интерфейса посредством Dispatcher.CheckAccess().
UIElement.Dispatcher.CheckAccess() сравнит идентификатор потока пользовательского интерфейса с идентификатором рабочего потока и возвратит значение «истина», если это один и тот же поток; в противном случае возвращается значение «ложь». Когда GetAgentScriptCallback исполняется в рабочем потоке (так как выполнение всегда будет происходить в рабочем потоке, по существу, можно просто вызывать Dispatcher.BeginInvoke), CheckAccess() вернет значение «ложь», и пользовательский интерфейс будет обновлен посредством направления анонимного делегата через Dispatcher.Invoke().