По рекомендации коллеги делаю краткий обзор новинок, появившихся в WCF.NET 4.0 betta 1. Судя по тому, что версия всего лишь betta, ориентироваться на нее стоит лишь приблизительно, но уже сам список новинок интересен. Оригинал статьи можно посмотреть тут http://msdn.microsoft.com/en-us/library/ee354381.aspx.
Итак, что же может появиться в составе .NET Framework версии 4:
- Упрощение конфигурирования. Появились параметры конфигурации по умолчанию, что делает конфигурирование проще и понятнее.
- Поддержка протокола WS-Discovery.
- Routing Service.
- Улучшение поддержки REST.
- Усиление интеграции WCF и WF.
Упрощение конфигурирования, параметры по умолчанию
Наверное, самое ожидаемое мною изменение. Практически вся настройка всевозможного вида каналов, биндингов, протоколов транспорта, настроек аутентификации, шифрования, сертификатов вынесено в конфигурацию. Конфиг превращается в кучу ссылающихся друг на друга разделов, которые достаточно непросто править вручную. Такой конфиг – ожидаемая плата за унифицированную модель программирования, которую предоставляет WCF. Теперь конфигурировать сервис должно быть проще.
Конфигурация Endpoint’ов
В версии 3.0, 3.5 если для ServiceHost не сконфигурированы Endpoint’ы, то генерировалось исключение. Теперь ServiceHost сам создаст Endpoint’ы, причем для каждого контракта, который реализует класс сервиса.
Конфигурация протоколов транспортного уровня
В Mashine.config теперь определяется маппинг дефолтовых протоколов. Теперь можно определить, какой binding будет использоваться для какого протокола по умолчанию. Выглядит этот раздел следующим образом:
<system.serviceModel>
<protocolMapping>
<add scheme= “http” binding= “basicHttpBinding” />
<add scheme= “net.tcp” binding= “netTcpBinding” />
<add scheme= “net.pipe” binding= “netNamedPipeBinding” />
<add scheme= “net.msmq” binding= “netMsmqBinding” />
</ProtocolMapping>
…
Такой же раздел можно засунуть и в конфигурацию сервиса, что облегчит жизнь, если нельзя менять конфигурацию машины.
Конфигурация Binding’ов
Каждый binding определяет параметры по умолчанию, которые используются, если вы явно их не переопределите в конфигурации или в коде. Раньше переопределение параметров для binding’а происходила только если привязать его к определенному endpoint’у. Теперь это делать необязательно. Можно не привязывать binding, параметры просто применятся как параметры по умолчанию для endpoint ‘ов, использующих binding нужного типа.
Конфигурация Behavior’ов
Также как и для binding’ов можно указывать неименованные behavior’ы. Например, можно по умолчанию включить WSDL описание через http:
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior> <!– notice no name attribute –>
<serviceMetadata httpGetEnabled=“true”/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Стандартные Endpoint’ы
Появилась конфигурация для стандартных endpoint’ов. Например, появился стандартная конфигурация для MEX конфигурации с контрактом IMetadataExchange. Для нее установлен mexHttpBinding как binding по умолчанию. Данная конфигурация используется очень часто для предоставления WSDL описания сервиса. Теперь конфигурирование сводится к следующим строкам:
<endpoint kind=“mexEndpoint” address=“mex” />
Стандартные наборы конфигураций полезны, когда необходимо сконфигурировать стандартные endpoint’ы или немного отличающиеся от них (изменением одного, двух параметров).
Выводы по конфигурации
Упрощение конфигурации делает конфигурацию читабельней, процесс создания сервисов проще. Такое упрощение с одной стороны сглаживает для разработчика переход с asmx сервисов на WCF. Однако обратная сторона этого упрощения – необходимость знать и помнить в Runtime настроек сервиса по умолчанию, ориентироваться на них.
Упрощение ASP.NET/IIS хостинга
Используя новые возможности конфигурации по умолчанию становятся возможными такие простые и элегантные сценарии, как:
<!– HelloWorld.svc –>
<%@ ServiceHost Language=“C#” Debug=“true” Service=“HelloWorldService
CodeBehind=”~/App_Code/HelloWorldService.cs” %>
[ServiceContract]
public class HelloWorldService
{
[OperationContract]
public string HelloWorld()
{
return “hello, world”;
}
}
Если заметите, и объявление сервиса, и объявление контракта сервиса происходит теперь в одном файле. Приведенное здесь объявление сервиса пожалуй самое минимальное что должно быть при создании сервиса.
При этом, при обращении к *.svc файлу WCF самостоятельно активирует ServiceHost, настроит для него endpoint по умолчанию. Если в Mashine.config также прописаны правила для предоставления описания сервиса по HTTP, оно также установится по умолчанию. На каждый контракт, который реализует сервис, создастся отдельный endpoint с параметрами по умолчанию.
Активация без файла
Если использовать для активации сервиса svc файлы, то endpoint будет иметь адресом привязки адрес svc файла. Таким образом, каждый endpoint, созданный по умолчанию будет иметь адрес svc файла плюс наименование контракта.
Однако имена svc файлов некрасиво смотрятся в URL, особенно если речь идет о REST сервисе. Используя IIS можно решить проблему, используя фильтрацию по URL адресам, однако, это вводит еще один уровень сложности для сервиса. Поэтому для активации в WCF 4 можно использовать новую конфигурацию serviceActivations:
<configuration>
<system.serviceModel>
<serviceHostingEnvironment>
<serviceActivations>
<add relativeAddress=“/Greeting” service=“GreetingService”/>
</serviceActivations>
</serviceHostingEnvironment>
</system.serviceModel>
</configuration>
Активацию такого вида и назвали красиво “file-less activation”.
Discovery
Это нововведение в WCF – попытка реализовать WS-Discovery протокол для обнаружения сервиса. Протокол позволяет определять местонахождение endpoint’ов сервиса. Клиент просматривает endpoint’ы сервиса и определяет, какой из них соответствует его критериям. Клиент может выбрать endpoint и использовать его адрес для доступа к сервису.
Discovery может работать в двух режимах: управляемом и специальном. В специальном режиме, сервис может посылать широковещательное сообщение всем клиентам, которые к нему могут быть подключены о смене сети или уходе из сети. Такой режим является достаточно простым, но работает только в рамках локальной подсети.
В управляемом режиме необходимо с использованием Discovery proxy управлять endpoint’ами. Сервис взаимодействует с прокси напрямую, причем такое взаимодействие может быть реализовано и через границы подсети. Клиенты при этом взаимодействуют напрямую с прокси, выбирая наиболее подходящий им endpoint, Discovery proxy реализовать нескольк сложнее, чем специальный режим, однако он более гибок и снижает широкополосный трафик сети.
Чтобы включить специальный режим необходимо добавить следующую конфигурацию:
<configuration>
<system.serviceModel>
<services>
<service name=“CalculatorService”>
<endpoint binding=“wsHttpBinding” contract=“ICalculatorService” />
<!– add a standard UDP discovery endpoint–>
<endpoint name=“udpDiscovery” kind=“udpDiscoveryEndpoint”/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDiscovery/> <!– enable service discovery behavior –>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
После этого, можно будет обнаруживать сервис с клиента по UDP. Клиенты могут, используя Discovery обнаружить реальный адрес сервиса и использовать его для доступа. Для этого необходимо сконфигурировать клиента:
<configuration>
<system.serviceModel>
<client>
<endpoint
name=“calculatorEndpoint”
binding=“wsHttpBinding”
contract=“ICalculatorService”>
</endpoint>
<endpoint name=“udpDiscoveryEndpoint” kind=“udpDiscoveryEndpoint”/>
</client>
</system.serviceModel>
</configuration>
С помощью класса DiscoveryClient можно сначала обнаружить адрес сервиса, а потом подключиться и использовать его для выполнения операций.
// Create DiscoveryClient
DiscoveryClient discoveryClient =
new DiscoveryClient( “udpDiscoveryEndpoint” );
// Find ICalculatorService endpoints in the specified scope
FindCriteria findCriteria = new FindCriteria(typeof(ICalculatorService));
FindResponse findResponse = discoveryClient.Find(findCriteria);
// Just pick the first discovered endpoint
EndpointAddress address = findResponse.Endpoints[0].Address;
// Create the target service client
CalculatorServiceClient client =
new CalculatorServiceClient( “calculatorEndpoint” );
// Connect to the discovered service endpoint
client.Endpoint.Address = address;
Console.WriteLine( “Invoking CalculatorService at {0}” , address);
// Call the Add service operation.
double result = client.Add(value1, value2);
Console.WriteLine( “Add({0},{1}) = {2}” , 100, 200, result);
Данный сценарий достаточно прост, клиент просто сканирует сеть для обнаружения нужного ему контаркта. Ограничить поиск можно за счет введения областей для сервиса.
Для того, чтобы сообщить что у сервиса появилась новая точка привязки, или сервис стартовал, можно использовать Service Announcements.
С помощью раздела <serviceDiscovery> можно настроить endpoint’ы, которые будут использоваться сервисом. Для большинства случаем может подойти endpoint udpAnnouncementEndpoint. При этом, все также нужно настроить сервис для использования udpDiscoveryEndpoint.
<configuration>
<system.serviceModel>
<services>
<service name=“CalculatorService”>
<endpoint binding=“wsHttpBinding” contract=“ICalculatorService”/>
<endpoint kind=“udpDiscoveryEndpoint”/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDiscovery>
<announcementEndpoints>
<endpoint kind=“udpAnnouncementEndpoint”/>
</announcementEndpoints>
</serviceDiscovery>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Для реализации событий перехода сервиса в состоянии online и offline реализован класс AnnouncementService. Клиент просто может создать экземпляр этого класса, начать слушать канал, после того, как сервис перейдет в то или иное состояние, он передаст широкополосное сообщение, которое вызовет событие на клиенте. Пример клиентского приложения может быть таким:
class Client
{
public static void Main()
{
// Create an AnnouncementService instance
AnnouncementService announcementService = new AnnouncementService();
// Subscribe the announcement events
announcementService.OnlineAnnouncementReceived += OnOnlineEvent;
announcementService.OfflineAnnouncementReceived += OnOfflineEvent;
// Create ServiceHost for the AnnouncementService
using (ServiceHost announcementServiceHost =
new ServiceHost(announcementService))
{
// Listen for the announcements sent over UDP multicast
announcementServiceHost.AddServiceEndpoint(
new UdpAnnouncementEndpoint());
announcementServiceHost.Open();
Console.WriteLine( “Listening for service announcements.” );
Console.WriteLine();
Console.WriteLine( “Press <ENTER> to terminate.” );
Console.ReadLine();
}
}
static void OnOnlineEvent(object sender, AnnouncementEventArgs e)
{
Console.WriteLine();
Console.WriteLine( “Received an online announcement from {0}:” ,
e.AnnouncementMessage.EndpointDiscoveryMetadata.Address);
PrintEndpointDiscoveryMetadata(
e.AnnouncementMessage.EndpointDiscoveryMetadata);
}
static void OnOfflineEvent(object sender, AnnouncementEventArgs e)
{
Console.WriteLine();
Console.WriteLine( “Received an offline announcement from {0}:” ,
e.AnnouncementMessage.EndpointDiscoveryMetadata.Address);
PrintEndpointDiscoveryMetadata(
e.AnnouncementMessage.EndpointDiscoveryMetadata);
}
…
}
Управляемый режим является просто некоторым расширением специального. Discovery proxy при этом, является компонентом, который отслеживает статус endpoint’ов. Для реализации Discovery proxy предназначен класс DiscoveryProxyBase. Его наследники и есть реализация нужного proxy класса. Например:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
public class DiscoveryProxy : DiscoveryProxyBase
Dictionary<EndpointAddress, EndpointDiscoveryMetadata> onlineServices;
public DiscoveryProxy()
{
this .onlineServices =
new Dictionary<EndpointAddress, EndpointDiscoveryMetadata>();
}
void AddOnlineService(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
{
lock ( this .onlineServices)
{
this .onlineServices[endpointDiscoveryMetadata.Address] =
endpointDiscoveryMetadata;
PrintDiscoveryMetadata(endpointDiscoveryMetadata, “Adding” );
}
}
void RemoveOnlineService(EndpointDiscoveryMetadata endpointDiscoveryMetadata)
{
if (endpointDiscoveryMetadata != null )
{
lock ( this .onlineServices)
{
this .onlineServices.Remove(endpointDiscoveryMetadata.Address);
PrintDiscoveryMetadata(endpointDiscoveryMetadata, “Removing” );
}
}
}
…
Сервис тогда примет примерно такой вид:
class Program
{
public static void Main()
{
Uri probeEndpointAddress = new Uri( “net.tcp://localhost:8001/Probe” );
Uri announcementEndpointAddress =
new Uri( “net.tcp://localhost:9021/Announcement” );
ServiceHost proxyServiceHost = new ServiceHost( new DiscoveryProxy());
DiscoveryEndpoint discoveryEndpoint = new DiscoveryEndpoint(
new NetTcpBinding(), new EndpointAddress(probeEndpointAddress));
discoveryEndpoint.IsSystemEndpoint = false ;
AnnouncementEndpoint announcementEndpoint = new AnnouncementEndpoint(
new NetTcpBinding(), new EndpointAddress(announcementEndpointAddress));
proxyServiceHost.AddServiceEndpoint(discoveryEndpoint);
proxyServiceHost.AddServiceEndpoint(announcementEndpoint);
proxyServiceHost.Open();
Console.WriteLine( “Proxy Service started.” );
Console.WriteLine();
Console.WriteLine( “Press <ENTER> to terminate the service.” );
Console.WriteLine();
Console.ReadLine();
proxyServiceHost.Close();
}
}
RSS читаю Я
Папа, Мама, Все семья!
Лишь Иван не подписался,
дурачком так и остался.