Одним из нескольких нововведений в Silverlight 3, которые касаются взаимодействия с WCF сервисами, является упрощение реализации двухстороней связи между клиентом и сервером.
Об этом хочу рассказать на примере создания простого чата. Для начало создадим новое Silverlight-приложение:
Добавляем WCF-сервис в наше ASP.NET-приложение:
Дальше, удаляем из файла-конфигурации веб-приложения описание endpoint’a с типом связывания wsHttpBinding:
<endpoint address="" binding="wsHttpBinding" contract="Chat.Web.IChatService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
После удаления, необходимо программно создать хост для сервиса. Для этого добавляем в проект веб-приложения новый файл:

и создаем в этом файле логику для хостинга сервиса с необходимым видом связывания. В нашем варианте нужно использовать специальный тип связывания CustomBinding
c PollingDuplexBindingElement (для типа PollingDuplexBindingElement необходимо в проект добавить ссылку на сборку System.ServiceModel.PollingDuplex):
using System;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Web;
usingSystem.ServiceModel;
usingSystem.ServiceModel.Channels;
namespaceChat.Web
{
public class ChatServiceHostFactory : ServiceHostFactoryBase
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses) { return new ChatServiceHost(baseAddresses);
}
}
classChatServiceHost : ServiceHost
{
public ChatServiceHost(params Uri[] addresses)
{
base.InitializeDescription(typeof(ChatService), new UriSchemeKeyedCollection(addresses));
}
protected override void InitializeRuntime()
{
PollingDuplexBindingElement pdbe = new PollingDuplexBindingElement()
{
ServerPollTimeout = TimeSpan.FromSeconds(3),
//Duration to wait before the channel is closed due to inactivity
InactivityTimeout = TimeSpan.FromMinutes(10)
};
this.AddServiceEndpoint(typeof(ChatService),
new CustomBinding(
pdbe,
new BinaryMessageEncodingBindingElement(),
new HttpTransportBindingElement()), string.Empty);
base.InitializeRuntime();
}
}
}
Теперь нужно в файле сервиса (ChatService.svc) указать созданый хост для сервиса. Выбираем пункт “View Markup” контекстного меню файла сервиса:

и заменяем существующую строку на:
<%@ServiceHost Language="C#" Debug="true" Factory="Chat.Web.ChatServiceHostFactory" Service="Chat.Web.ChatService" %>
Проверяем измененный сервис в браузере:
Теперь надо создать интерфейс взаимодействия сервиса с клиентом:
[ServiceContract]
public interface IChatService
{
[OperationContract(IsOneWay = true, AsyncPattern=true)]
IAsyncResult BeginUserConnected(string UserName, AsyncCallback callback, object state);
void EndUserConnected(IAsyncResult result);
[OperationContract(IsOneWay = true, AsyncPattern=true)]
IAsyncResult BeginUserDisconnected(string UserName, AsyncCallback callback, object state);
void EndUserDisconnected(IAsyncResult result);
[OperationContract(IsOneWay = true, AsyncPattern=true)]
IAsyncResult BeginSendMessage(string Message, AsyncCallback callback, object state);
void EndSendMessage(IAsyncResult result);
}Связь с клентом осуществляется асинхронно, так как отправка сообщения одному пользователю не должна задерживать отправку сообщения другому.
, так как отправка сообщения одному пользователю не должна задерживать отправку сообщения другому.
Используя синхронный подход, попадем в следующую ситуацию - когда пропадает связь с пользователем, то сервис ждет определеное время, что бы связаться с пользователем.
Если же связь установить не удается, вылетает исключение тайм аута. При этом рассылка сообщения другим пользователям задерживается.
Дальше необходимо указать сервису интерфейс работы с клиентом. Это задается на уровне атрибута для сервиса:
[ServiceContract(Namespace = "", CallbackContract = typeof(IChatService))]
public class ChatService
{…}Создаем логику работы сервиса. Для начала добавим метод, который подключает пользователей к чату. В случае успешного подключения,
метод возвращает клиенту True:
[OperationContract]
public bool Connect(string userName)
{
var result = false;
if (!Users.Keys.Contains(userName))
{
var clientChannel = OperationContext.Current.GetCallbackChannel<IChatService>();
… result = true;
}
return result;
}Внутри метода получаем канал соединения с клиентом: OperationContext.Current.GetCallbackChannel<IChatService>()
По этому каналу сможем передавать необходимые данные пользователю с сервера. Например, что бы сообщить клиенту о подключении нового пользователя вызываем метод:
clientChannel.BeginUserConnected(newUserName, new AsyncCallback(EndUserConnected), null);
В Silverlight-приложении необходимо добавить ссылку на сервис и реализовать логику обработки сообщений сервиса.
При создании прокси, для работы с сервисом, необходимо программно создать адрес сервиса и тип связывания с ним:
address = new EndpointAddress("http://localhost:12345/ChatService.svc");
binding = new CustomBinding(
new PollingDuplexBindingElement(),
new BinaryMessageEncodingBindingElement(),
new HttpTransportBindingElement()
);
proxy = new ChatServiceClient(binding, address);После создания необходимой логики запускаем приложение в нескольких окнах браузера:
Скачать решение.
P.S.: В сервис необходимо добавить логику для многопоточности!
Дополнительные ссылки: