Создание бизнес-приложений с помощью 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().