Ласкаво просимо до dev.net.ua Увійти | Приєднатися | Допомога
UNETA 18/11/2011: Александр Фещенко - Внутренняя организация Windows Azure, Виталий Тромпак - Обзор возможностей HTML 5

Встреча пройдет в Харьковском национальном университете радиоэлектроники 18 ноября 2011 года. Начало в 18:30.

Описание докладов можно посмотреть на новом сайте юзер-группы - UNETA.UA

Как украинские MSP в Финляндию ездили…

   Дорогие друзья, на прошлой неделе мне выпала честь войти в команду, которая представляла Украину на международном саммите MSP (студентов-партнеров Microsoft), который проходил в столице Финляндии городе Хельсинки.

  Сразу хочу сказать спасибо за такую возможность департаменту стратегических технологий компании Microsoft Украина и лично Сергею Байдачному.  

  Данное мероприятие носит название Nordic-Baltic MSP Summit и проводится ежегодно для стран Прибалтики и Северной Европы. Однако, в этом году организаторы  также решили пригласить делегации из Украины и России.

   В украинскую команду, кроме меня, вошли Тарас Рошко из Львова и Михаил Сенченко из Мелитополя.

  

Расстояние от Киева до Хельсинки примерно 1200 километров, его мы преодолели со скоростью 800 км/час за 1 час 45 минут. Для большинства членов нашей команды это был первый в жизни полет, поэтому мы испытали по-настоящему потрясающие ощущения, наблюдая за затянутой облаками Европой с высоты 12 километров.

  

  В аэропорту Ванта города Хельсинки нас встретило такси, и мы направились в офис компании Microsoft Финляндия, где мы познакомились с нашими коллегами из других стран и организаторами саммита – сотрудниками финского офиса Microsoft - Жаклин Рассел и Петри Вихелмсен.

  

   Традиционно, в офисе Microsoft народ развлекался, играя в новые игры для приставки Xbox и Kinect.  

   Вечером, после поселения в отель, была проведена сессия презентаций, где каждый должен был рассказать о себе, а делегации сделать презентацию страны, вкратце обрисовав свою деятельность, как партнеров Microsoft, и локальные тенденции в сфере IT.

   Переутомившись после такого мероприятия, все наслаждались отличным ужином, бассейном в отеле и традиционной финской сауной с местными сортами пива Smile.

  На следующий день сотрудники Microsoft и европейские эксперты рассказывали студентам о платформе Windows Phone и технологиях разработки мобильных приложений.   

  Кульминацией дня стал момент, когда нас всех разбили на команды и сказали разработать за вечер и ночь приложение для Windows Phone.

  Это было хорошим сюрпризом для всех (особенно для меня, как для веб-разработчика). При этом в каждой команде все люди были из разных стран, что сделало задачу еще более интересной. Однако все отлично владели английским, и к следующему утру был готов прототип приложения для Windows Phone. И хотя мы использовали немного устаревшую версию Power Point Smile, мы сумели сделать одну из лучших презентаций.   

   Вечером, в качестве интерактива нас отвезли в развлекательный центр, где мы могли проявить свои навыки в беге и стрельбе, сражаясь в лазерные бои.  

   Ну, и как всегда бывает в таких ситуациях, крайне быстро пришло время расставаться и возвращаться обратно в Киев. Эти три дня были лучшим приключением в моей жизни, мы приобрели море новых друзей и удивительных ощущений. И конечно, я благодарен своей команде за потрясающее время, которое мы провели вместе: Таня, Stian, Ignas и Hans – я всех вас помню, и мы обязательно встретимся снова!

   И закончить свой рассказ, я бы хотел словом, которое было много раз произнесено за время этого саммита и которое теперь у меня всегда будет ассоциироваться только с ним – AWESOME!

UNETA 28/10/2011: Anatoliy 'TLK' Kolesnick: Ко- и контравариантность в C#, Левон Гюльназарян: Motion API или Windows Phone в пространстве

 

Приглашаем всех желающих 28 октября на очередную встречу профессиональных .NET разработчиков – UNETA.

На встрече будут представлены два доклада:

1. Докладчик: Anatoliy 'TLK' Kolesnick - Senior .NET Developer in Direct EDI

    Тема доклада: Ко- и контравариантность в C#.

    Доклад охватывает следующие темы:

  • Знакомство с понятием ковариантности и контравариантностью;
  • Логические ограничения, накладываеме на ко- и контравариантые сущности;
  • Примеры ко- и контравариативных сущностей;
  • Ко- и контравариантность в ранних версиях C#;
  • Подробнее о третьем логическом ограничении;
  • Ко- и контравариантность в Scala.

2. Докладчик: Левон Гюльназарян - XNA developer in mobile department in DCT

    Тема доклада: Motion API или Windows Phone в пространстве.

    В докладе будут освещены следующие темы:

     Что такое Windows Phone 7:

  • Кратко об устройстве;
  • Какие датчики в него встроены;
  • Что такое Motion API;

     Что такое XNA:

  • Что предоставляет XNA;
  • XNA в Windows phone 7;
  • Использование Motion API;
  • XNA + Silverlight = FUN!;

 

Встреча будет проходить в Харьковском национальном университете радиоэлектроники, пр. Ленина, 14 (ст.м. Научная) 329 ауд. - 28 октября 2011 г. в 18-30 

Собрания UNETA проходят при поддержке Microsoft Innovation Center|Kharkov  и компании DCT.

WCF – это просто: Контракты сообщений

Мы продолжаем тему работы с данными в сервисах WCF. В предыдущих статьях мы познакомились с контрактами данных, как способом передачи данных между сервисом и клиентом, и теперь мы рассмотрим один из альтернативных способов – контракты сообщений.

Как известно, WCF сервисы работают с клиентами посредством SOAP сообщений, содержащих передаваемую информацию в сериализованном виде. По сравнению с контрактами данных, контракты сообщений позволяют на более низком уровне, полностью управлять содержимым SOAP сообщений.

Соответственно, использование контрактов сообщений, а не контрактов данных, требуется в случае желания разработчика полностью контролировать содержимое SOAP сообщения. Для внесения ясности в различие с контрактами данных - при использовании контракта данных мы можем контролировать только тело SOAP сообщения, но не его заголовки.

Мы рассмотри следующие аспекты, связанные с контрактами сообщений:

  1. Использование контрактов сообщений в WCF;
  2. Установка SOAP заголовков в контрактах сообщений;
  3. Настройка защиты контрактов сообщений;
  4. Управление версиями контрактов сообщений;
  5. Наследование в контрактах сообщений;
  6. Производительность контрактов сообщений.

 

Использование контрактов сообщений в WCF

Процесс создания контрактов сообщений в WCF очень похож на процесс создания контрактов данных, только вместо атрибутов DataContract и DataMember, используется следующий набор атрибутов:

  1. MessageContract – этим атрибутом помечается класс, который выступает в качестве контракта сообщения;
  2. MessageHeader – помечаются поля и свойства класса, которые будут сериализоваться в разделе заголовков SOAP сообщения;
  3. MessageBodyMember – помечаются поля и свойства класса, которые будут сериализоваться в тело SOAP сообщения.

Рассмотрим следующий пример объявления контракта сообщения:

    [MessageContract]
    public class Customer
    {
        [MessageHeader]
        public int Id;

        [MessageHeader]
        public string Name;

        [MessageHeader]
        public DateTime DateCreated;

        [MessageBodyMember]
        public string Info;

        [MessageBodyMember]
        public string Gender;

        [MessageBodyMember]
        public int ItemsCount;
    }

В результате сериализации данного класса получится приблизительно следующее SOAP сообщение:

<soap:Envelope>
   <soap:Header>
       <Id>1</Id>
       <Name>Jack</Name>
       <DateCreated>05/22/2010</DateCreated>
   </soap:Header>
   <soap:Body>
       <Info>No additional info</Info>
       <Gender>man</Gender>
       <ItemsCount>10</ItemsCount>
   </soap:Body>
</soap:Envelope>

Теперь посмотрим, как можно использовать созданный нами контракт сообщения в операциях сервис. Представленный выше класс будет выступать входящим сообщением для операции сервиса. Создадим еще один контракт сообщения, который будет возвращаемым объектом метода сервиса:

[MessageContract]
public class CustomerResponse 
{
    [MessageHeader]
    public int CustomersCount;

    [MessageBodyMember]
    public DateTime LastCustomerHasBeenAddedAt;
}

Теперь создадим простой сервис, который будет использовать наши контракты:

    [ServiceContract]
    public interface ICustomersService
    {
        [OperationContract]
        CustomerResponse AddCustomer(Customer cus);
    }

    public class CustomService : ICustomersService
    {
        public CustomerResponse AddCustomer(Customer cus)
        {
            // some logic here            
        }
    }

В данном примере метод AddCustomer оперирует данными при помощи контрактов сообщений. Метод, который использует контракты сообщений, может иметь только один входящий параметр, который должен быть контрактом сообщения. Возвращаемый тип данных также должен быть отмечен атрибутом MessageContract. Таким образом объявление следующих методов будет некорректным и вызовет ошибку:

        [OperationContract]
        bool ValidateCustomer(Customer customer);

        [OperationContract]
        CustomerResponse AddCustomers(Customer cus_1, Customer cus_2);

Еще один атрибут, связанный с контрактами сообщений – MessageHeaderArrayAttribute. Этим атрибутом отмечаются поля в контрактах сообщений, имеющие тип – массив данных. Однако, также возможно помечать массив просто атрибутом MessageHeader или MessageBodyMember. Различие этих  двух способов заключается в способе сериализации массива в SOAP сообщение. Рассмотрим следующий пример:

        [MessageHeader]
        public int[] records;

В этом случае в результате сериализации получиться приблизительно следующий XML код:

<records>
  <int>1</int>
  <int>2</int>
  <int>3</int>
</records>

Если же мы используем специальный атрибут MessageHeaderArrayAttribute:

        [MessageHeaderArray]
        public int[] records;

Теперь массив целых чисел сериализуется независимо и каждый элемент массива будет выступать в сериализованном сообщении отдельным заголовком:

<records>1</records>
<records>2</records>
<records>3</records>

Взаимодействие с контрактами данных. Класс, выступающий контрактом сообщения и помеченный атрибутом MessageContract, может быть также отмечен атрибутом DataContract, но в этом случае будет учитываться только контракт сообщения, а все связанное с контрактом данных будет проигнорировано.

 

Установка SOAP заголовков в контрактах сообщений

Одна из ключевых возможностей, из-за которой разработчики используют контракты сообщений, а не контракты данных – это возможность управления заголовками SOAP сообщения. Спецификация протокола SOAP предусматривает следующие заголовки в SOAP сообщениях:

  1. Actor/Role (Actor в спецификации SOAP 1.1, Role в спецификации SOAP 1.2) – задает универсальный идентификатор ресурса(URI адрес), для которого предназначен данный заголовок;
  2. MustUnderstand – определяет должен ли обрабатывающий заголовок ресурс синтаксически его понимать;
  3. Relay – определяет будет ли сообщение после передачи на принимающий ресурс передаваться на другие узлы.

Установка данных атрибутов производится при помощи следующего синтаксиса:

    [MessageContract]
    public class CustomerAuth
    {
        [MessageHeader(Actor="authservice.wcfworld.com", MustUnderstand=true, Relay=false]
        public bool AuthMember;        
    }

Стоит отметить, что механизм WCF игнорирует данные заголовки во входящих сообщения, за исключение заголовка MustUnderstand.

 

Защита контрактов сообщений

Механизм WCF позволяет снабжать заголовки и тело сообщения элементами защиты, такими как шифрование и цифровая подпись. Это делается при помощи свойства ProtectionLevel в атрибутах MessageHeader и MessageBodyMember. Это свойство является перечислением типа System.Net.Security.ProtectionLevel и может принимать следующие значения:

  1. None – без защиты;
  2. Sign – защита путем создания цифровой подписи;
  3. EncryptAndSign – шифрование данных и создание цифровой подписи.

По умолчанию,  для заголовков и тела сообщения установлен наивысший уровень защиты – EncryptAndSign.

Однако, чтобы эти атрибуты корректно сработали необходимо правильно указать привязку (binding) и дополнительные параметры. При попытке использовать эти атрибуты без настройки, например при попытке создания цифровой подписи без предоставления учетных данных (сертификата), будет вызвано исключение.

Для тела и заголовков контракта сообщение атрибуты безопасности применяются по разному. Каждое поле в заголовке защищается отдельно, в зависимости от значения собственного атрибута. Но тело сообщения всегда имеет один уровень защиты, который определяется наивысшим значением защиты среди полей тела. Рассмотрим пример:

    [MessageContract]
    public class Customer
    {
        [MessageHeader(ProtectionLevel = ProtectionLevel.None)]
        public int ID;

        [MessageHeader(ProtectionLevel = ProtectionLevel.Sign)]
        public string Name;

        [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string CreditCardNumber;

        [MessageBodyMember(ProtectionLevel = ProtectionLevel.None)]
        public string Comments;

        [MessageBodyMember(ProtectionLevel = ProtectionLevel.Sign)]
        public string ItemsList;

        [MessageBodyMember(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public string PersonalInfo;
    }

В данном случае в заголовке сообщения: поле ID будет не защищено, поле Name будет подписано цифровой подписью, а поле CreditCardNumber будет зашифровано и подписано. Все поля в теле сообщения будут иметь наивысший уровень защиты – будут подписываться и шифроваться, так как у поля PersonalInfo свойство ProtectionLevel установлено в EncryptAndSign. И хотя все поля тела сообщения автоматически получают максимальный указанный уровень защиты, рекомендуется указывать для каждого поля собственный минимальный уровень необходимой защиты.

 

Управление версиями контрактов сообщений

Как и в случае с контрактами данных, при использовании контрактов сообщений могут возникать ситуации, когда версия контракта у некоторых клиентов может не соответствовать текущей  версии контракта на сервисе. Рассмотрим следующий пример, пусть класс А – это контракт сообщения у клиентского приложения, а код B – это текущий контракт сервиса:

A:  public class Customer                   B:  public class Customer
    {                                           {
        [MessageHeader]                             [MessageHeader]
        public int Id;                              public int Id;

        [MessageBodyMember]                         [MessageHeader]
        public string Info;                         public string Name;
    }
                                                    [MessageBodyMember]
                                                    public string Info;

                                                    [MessageBodyMember]
                                                    public string CreditCardNumber;
                                                }

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

  1. В случае недостающих полей подставляются значения по умолчанию(в примере – после того как клиент отправит сервису класс А, сервис преобразует его в класс B, при этом подставив в поля  Name и CeditCardNumber значение null);
  2. В случае лишних полей – данные из неизвестных полей игнорируются(после того как сервис отправит класс B клиенту, клиент преобразует его в класс А, отбросив поля Name и CeditCardNumber).

 

Наследование в контрактах сообщений

Контракты сообщений могут быть частью иерархии наследования. А если точнее, классы, помеченные атрибутом MessageContract могут наследоваться от других классов, помеченных атрибутом MessageContract. В этом случае происходит следующее:

  1. Собираются все заголовки из всех классов в иерархии наследования и образуют раздел заголовков для конечного сообщения;
  2. Собираются все элементы тела сообщения из всех классов в иерархии наследования, упорядочиваются по значению свойства Order, а затем по алфавиту и формируют конечное тело сообщения.

В этом случае возможен вариант, когда поля с одинаковым именем существуют в нескольких классах одной иерархии наследования, в таком случае в конечное сообщение попадет только поле самого базового класса в цепочке наследования. Рассмотрим следующий пример:

    [MessageContract]
    public class Person
    {
        [MessageHeader(Name = "ID")]
        public int PersonId;
        
        [MessageBodyMember]
        public string Gender;
        
        [MessageBodyMember]
        public string Name;
    }

    [MessageContract]
    public class Customer : Person
    {
        [MessageHeader(Name = "ID")]
        public int CustomerId;

        [MessageBodyMember]
        public string ItemsList;
    }

В данном примере для поля PersonId класса Person и для поля CustomerId класса Customer указано одно и тоже имя для сериализации. При таком сценарии в конечное сообщение будет включено только поле базового класса, то есть поле PersonId, а поле CustomerId будет проигнорировано. Конечно, такой сценарий является нежелательным, и разработчикам следует его, по возможности, избегать.

 

Производительность контрактов сообщений

Производительность контракта данных – это, в основном, время, затраченное на сериализацию хранящихся в нем данных. Чтобы оптимизировать этот параметр рекомендуется не дублировать в нем атрибуты MessageHeader и MessageBodyMember. В идеальном случае они должны встречаться один раз, это в значительной степени сможет укорить процесс сериализации. Рассмотрим следующий пример:

    [MessageContract]
    public class Customer
    {
        [MessageHeader]
        public int Id;

        [MessageBodyMember]
        public string Name;

        [MessageBodyMember]
        public string Gender;

        [MessageBodyMember]
        public string Info;
    }

Для оптимизации производительности его следует переписать следующим образом:

    [MessageContract]
    public class Customer
    {
        [MessageHeader]
        public int Id;

        [MessageBodyMember]
        public CustomerInfo Info;
    }

    [DataContract]
    public class CustomerInfo
    {
        [DataMember]
        public string Name;

        [DataMember]
        public string Gender;

        [DataMember]
        public string Info;
    }

 

Выводы

В данной статье мы познакомились с контрактами сообщений, выяснили в чем состоит разница между контрактами сообщений и контрактами данных. Научились использовать контракты сообщений при построении WCF сервисов, узнали о предусмотренных механизмах защиты, а также об оптимизации производительности при работе с данными. Приятной работы с технологией WCF!

WCF – это просто: Настройка поведения сервисов

Инфраструктура механизмов Windows Communication Foundation построена таким образом, что каждый ее элемент обладает определенным поведением. И разработчики, в процессе программирования сервиса, конфигурируют это поведение. В прошлой статье мы упоминали о настройках поведения конечных точек(endpoints), а сегодня рассмотрим параметры конфигурации поведения сервиса в целом – Service Behavior.

 

Service Behavior

Управление поведением в масштабах всего сервиса устанавливается при помощи свойств атрибута ServiceBehavior. Этим атрибутом может быть отмечен только  класс, который инкапсулирует в себе весь функционал сервиса, реализуя все контракты операций сервиса. Альтернативный способ – использование файла конфигурации. Мы рассмотрим первый способ. Итак, атрибут ServiceBehavior имеет следующие свойства:

  1. AddressFilterMode – устанавливает тип фильтра, который используется механизмом маршрутизации WCF для направления входящих запросов на нужную конечную точку, путем применения данного фильтра для сравнения запрашиваемого адреса и реального адреса расположения точек. Свойство может принимать следующие значения из перечисления System.ServiceModel.AddressFilterMode: Exact, Prefix и Any;
  2. AutomaticSessionShutdown – логическое свойство, которое определяет должен ли сервис автоматически завершать сеанс, когда клиент закрывает соединение с сервисом. По умолчанию установлено значение true и сеанс сервиса существует, пока открыто соединение с клиентом. Если установить свойство AutomaticSessionShutdown в false – то автоматическое прерывание отключается и можно контролировать время жизни сеанса в коде приложения сервиса;
  3. ConcurrencyMode – устанавливает режим потоковой работы сервиса. В зависимости от значения данного свойства сервис может выполнять операции в однопоточном или многопоточном режимах. Свойство может принимать следующие значения из перечисления System.ServiceModel.ConcurrencyMode:
    • Single – значение по умолчанию, экземпляр сервиса работает в однопоточном режиме и не допускает обратных вызовов;
    • Reentrant – сервис работает в однопоточном режиме, но допускает повторные вызовы;
    • Multiple – сервис работает в многопоточном режиме, при этом механизмы WCF не утруждают себя синхронизацией, поэтому работая в этом режиме не забывайте, при необходимости, программно обеспечивать синхронизацию потоков.
  4. ConfigurationName – задает имя соответствующей секции behavior в конфигурационном файле;
  5. IgnoreExtensionDataObject – логическое свойство, которое определяет будет ли сервис обрабатывать данные, хранящиеся в свойстве ExtensionDataObject контракта данных. Данное свойство используется для обеспечения совместимости разных версий контрактов данных, в него записываются все неизвестные поля и свойства при десериализации сервисом входящего сообщения. По умолчанию установлено в true, рекомендуется устанавливать в значение false, если требуется строгое соответствие схемы при работе клиентов с сервисом(подробно об использовании интерфейса IExtensibleDataObject рассматривалось в статье WCF – это просто: Продвинутое управление контрактами данных);
  6. IncludeExceptionDetailInFaults – логическое свойство, которое определяет, должны ли ошибки возникающие на стороне сервиса преобразовываться к типу System.ServiceModel.FaultException и передаваться в ответном сообщении клиенту, который инициировал запрос. По умолчанию данная функция отключена, ее следует включать в целях отладки сервиса;
  7. InstanceContextMode – определяет режим создания новых экземпляров сервиса. В конечном счете, сервис представляет собой нестатический класс, который реализует контракты операций сервиса. И, соответственно, перед тем как вызывать его методы, необходимо создать экземпляр данного класса. Данное свойство определяет каким образом механизм WCF будет создавать новые экземпляры класса-сервиса, свойство может принимать следующие значения из перечисления System.ServiceModel.InstanceContextMode:
    • PerSession – режим по умолчанию – для каждого сеанса(сессии) создается свой экземпляр сервиса(когда клиент открывает соединение с сервисом), который используется для всех вызовов во время данного сеанса и удаляется по его завершению;
    • PerCall – для каждого обращения клиента к сервису создается новый экземпляр класса-сервиса, который удаляется по завершению вызова;
    • Single – все время работы сервиса будет существовать только один экземпляр класса-сервиса. При первом обращении любого клиента создается новый экземпляр сервиса, который будет обрабатывать все последующие запросы и не удалиться никогда.
  8. MaxItemsInObjectGraph – свойство определяет максимальное число полей во входящем сообщении, которые необходимо будет десериализовать;
  9. Name и Namespace – эти два свойства определяют имя и пространство имен сервиса, которые будут отображаться в сгенерированном WSDL коде;
  10. ReleaseServiceInstanceOnTransactionComplete – логическое свойство, которое определяет будет ли объект сервиса автоматически удаляться после завершения транзакции;
  11. TransactionAutoCompleteOnSessionClose – логическое свойство, определяющее завершаются ли автоматически все транзакции при закрытии сессии(сеанса связи клиент-сервис);
  12. TransactionIsolationLevel – свойство типа System.Transactions.IsolationLevel, которое определяет политику блокировок, применяемую к транзакциям, которые обрабатывает сервис(транзакции в WCF – достаточно обширная тема, поэтому я не буду описывать подробно атрибуты, связанные с транзакциями. В скором будущем я планирую написать об этом отдельную статью);
  13. TransactionTimeout – свойство указывает период времени, в течении  которого транзакция должна завершиться. После его истечения транзакция будет автоматически прервана;
  14. UseSynchronizationContext – логическое значение, определяющее должен ли сервис использовать текущее значение SynchronizationContext для выбора потока исполнения(значение по умолчанию – true);
  15. ValidateMustUnderstand – логическое свойство, определяющее необходимо ли строго контролировать, смог ли сервис понять все заголовки во входящем SOAP сообщении, которые предварительно были отмечены атрибутом MustUnderstand.

Путем комбинации вышеперечисленных свойств можно создавать для сервиса правила поведения. Однако, будьте осторожны – здесь тоже есть свои подводные камни. Например, если установить оба свойства ConcurrencyMode и InstanceContextMode в значение Single – то в этом случае будет создан лишь один объект сервиса и он будет работать в однопоточном режиме. Такая конфигурация приведет к тому, что сервис будет способен одновременно обрабатывать лишь один запрос от клиента. В случае, когда сервис активно используется множеством клиентом, это приведет к созданию очереди запросов на стороне сервиса. Если при этом сервис еще выполняет долговременную, тяжелую операцию – то такая конфигурация сделает вашу систему полностью неработоспособной. Чтобы этого избежать, рекомендуется при установке свойства InstanceContextMode в значение Single, задавать в свойстве ConcurrencyMode многопоточный режим работы для сервиса(значение Multiple).

Рассмотрим пример, в котором устанавливаются вышеописанные свойства поведения для сервиса, при помощи атрибута ServiceBehavior:   

    [ServiceContract]
    public interface ITestService
    {
        [OperationContract]
        int CalculateSum(int a, int b);
    }

    [ServiceBehavior(
        ConcurrencyMode = ConcurrencyMode.Single,
        InstanceContextMode = InstanceContextMode.PerSession,
        IncludeExceptionDetailInFaults = true,
        ReleaseServiceInstanceOnTransactionComplete = true
    )]
    public class TestService : ITestService
    {
        public int CalculateSum(int a, int b)
        {
            return a + b;
        }
    }
В данном примере наш сервис будет работать в одном потоке, новый объект сервиса будет создаваться при открытии канала клиентом и будет удаляться по завершению каждой транзакции. Также, в целях отладки, включена опция отправки клиентам подробной информации об ошибках на сервисе.

 

Расширение ServiceBehavior

В примере выше мы использовали атрибут ServiceBehavior для задания параметров поведения сервиса. Класс, представляющий данный атрибут имеет следующее объявление:

public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior { ... }

Как видно, данный класс реализует интерфейс IServiceBehavior. Данный интерфейс предоставляет методы для внедрения расширений в параметры поведения служб WCF. Посмотрим на описание этого интерфейса:

    public interface IServiceBehavior
    {
        void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
        void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, 
            Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters);
        void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase);
    }

Методы интерфейса предназначены для следующих целей:

  1. AddBindingParameters – предназначен для внедрения данных в элементы привязки текущего сервиса. Используется некоторыми механизмами безопасности для применения идентификационных данных клиентов. Данный метод отличается от любым других методов расширения поведения тем, что вызывается для каждой конечной точки(остальные методы вызываются только один раз);
  2. ApplyDispatchBehavior – предоставляет возможности для вставки объектов настраиваемых расширений, например, обработчиков ошибок, перехватчиков параметров или сообщений, а также других объектов настраиваемых расширений;
  3. Validate – обеспечивает проверку соответствия описания службы и основного приложения хостинга. Ярким примером использования является атрибут AspNetCompatibilityRequirementsAttribute, который также реализует интерфейс IServiceBehavior и использует метод Validate для проверки установленной политики режима совместимости с ASP.NET на сервисе и режима совместимости, предоставляемого основным приложением. Например, если на сервисе установлен атрибут AspNetCompatibilityRequirements в режим AspNetCompatibilityRequirementsMode.Required (в этом случае сервис требует обязательное наличие совместимости для хостинга в среде приложения ASP.NET), но такой режим совместимости не предоставляется хостингом(например, если сервис хоститься отдельным WCF проектом) – то при обращении к сервису возникнет исключение.

Теперь, когда мы познакомились с содержимым интерфейса IServiceBehavior, мы готовы написать свое расширение поведения сервиса. Для этого необходимо создать свой класс и реализовать интерфейс IServiceBehavior, внеся определенный код в необходимые методы:

    public class MyServiceBehavior : IServiceBehavior
    {
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {
            Type serviceType = serviceDescription.ServiceType;
            MethodInfo[] methods = serviceType.GetMethods(BindingFlags.Instance | BindingFlags.Public)
                .Where(m => m.DeclaringType == serviceType).ToArray();
            if (methods.Length == 0)
                throw new InvalidOperationException("Service does not have any public methods!");

            foreach (MethodInfo method in methods)
            {
                foreach (ParameterInfo parameter in method.GetParameters())
                {
                    if (parameter.ParameterType.IsByRef)
                    {
                        throw new InvalidOperationException("This service does not support methods with out or ref parameters!");
                    }
                }
            }
        }

        public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, 
                        Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
        {  }

        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
        {  }
    }

В данном примере внесли код в метод Validate, при этом не дополняя методы AddBindingParameters и ApplyDispatchBehavior. Код в методе Validate выполняет две функции:

  1. В массив methods  мы получаем список всех публичных методов текущего контракта операций. Если не было найдено ни одного метода – мы генерируем исключение с сообщением об этом;

  2. Если методы найдены – мы перебираем в цикле все параметры каждого метода и если хотя бы один из них имеет модификатор ref или out – генерируется исключение(только для примера).

Теперь осталось только применить наш класс расширения поведения к сервису, сделать это можно в файле конфигурации:

  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="MyServiceBehavior" type="WCFMultiContract.MyServiceBehavior, HostApplication, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior name="UsingMyServiceBehavior">
          <MyServiceBehavior/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="WCFMultiContract.MyService" behaviorConfiguration="UsingMyServiceBehavior">
        <endpoint />
        ...
      </service>
    </services>
  </system.serviceModel>

 

Выводы

Сегодня познакомились c настройками поведения WCF сервисов и научились это поведение расширять.

WCF – это просто: Endpoints

Всем привет! Сегодня мы поговорим о Endpoints в WCF. Endpoint или конечная точка, по сути, является окном, через которое происходит взаимодействие клиента с сервисом WCF. Мы рассмотрим следующие аспекты, связанные с Endpoints в WCF:

  1. Что, собственно, такое Endpoint и какую роль он играет в инфраструктуре сервиса WCF;
  2. Упрощенная конфигурация и стандартные Endpoints в WCF 4;
  3. Настройка поведения конечной точки – Endpoint Behavior.

 

Что такое Endpoint?

Как всем известно, в основе технологии WCF лежит три базовых составляющих:

  1. Address – определяет куда следует отправлять сообщения;
  2. Binding – определяет как необходимо отправлять сообщения;
  3. Contract – определяет что должно содержать сообщение.

И именно в Endpoint эти три составляющие соединяются вместе. Рассмотрим пример конфигурационного файла, в который внесены конфигурации конечных точек сервиса:

<configuration>  
  <system.serviceModel>    
    <services>
      <service name="WCFMultiContract.Service" behaviorConfiguration="defaultBehavior">      
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <endpoint address="simple" binding="wsHttpBinding" contract="WCFMultiContract.ISimpleCalculator" />        
      </service>
    </services>
  </system.serviceModel>  
</configuration>

В предыдущих статьях мы не раз использовали похожую конфигурацию. Давайте теперь разберемся с ней подробней. Как видно из примера для каждого endpoint указываются address, binding и contract. В данном случае были созданы две конечные точки:

  1. Mex точка, служащая для получения метаданных сервиса клиентами. Это стандартный endpoint.
  2. Вторая точка непосредственно используется для выполнения операций между клиентами и сервисом. Через нее клиенты сервиса могут вызывать методы сервиса, объявленные в контракте этой конечной точки (в данном случае – методы интерфейса ISimpleCalculator).

 

WCF Endpoints в .NET 4

Перед разработчиками WCF в .NET 4 стояла серьезная задача. В версии 3.5 была очевидная проблема, когда неопытные разработчики могли тратить на написание конфигурации сервиса больше времени, чем собственно на написание его функций. Поэтому команда разработчики WCF 4 постарались максимально упростить для программистов процесс конфигурации сервисов, тем самым дав им возможность сосредоточится на реализацию функций сервиса. В связи с этим были определены ряд параметров конфигурации по умолчанию, в том числе и стандартная конфигурация для конечных точек сервисов. Эти конфигурации были созданы, чтобы охватить наиболее часто используемые комбинации свойств конечных точек. Ниже приведен их список и краткое описание:

  1. MEXEndpoint – определяет стандартную точку получения метаданных сервиса, туда заложен стандартный контракт IMetadataExchange и привязка mexHttpBinding;
  2. AnnouncementEndpoint – определяет стандартную конечную точку с фиксированным контрактом объявления(WCF сервис может объявлять свою доступность путем отправки сообщений в режиме в сети/не в сети);
  3. DiscoveryEndpoint – определяет конечную точку с контрактом обнаружения. При добавлении в конфигурацию сервиса, указывает где необходимо следить за появлением сообщений обнаружения, а при добавлении в конфигурацию клиента – указывает куда необходимо отправлять сообщения обнаружения;
  4. UdpDiscoveryEndpoint – определяет стандартную конечную точку с контрактом обнаружения, настроенную для операций обнаружения по привязке многоадресной рассылки UDP. Данная точка поддерживает две версии протокола WS-Discovery, а также имеет привязку UDP и значение адреса по умолчанию;
  5. UdpAnnouncementEndpoint – определяет стандартную конечную точку, используемую службами для отправки сообщений с объявлениями по привязке UDP. Имеет фиксированный контракт, привязку UDP и значение адреса по умолчанию;
  6. DynamicEndpoint – содержит данные стандартной конечной точки, используемой на стороне клиента сервиса. Данная конфигурация позволяет клиентскому коду определить адрес конечной точки сервиса в динамическом режиме, во время выполнения программы;
  7. WebHttpEndpoint – определяет стандартную конечную точку сервиса, настроенную для работы на привязке webHttpBinding. Данная конфигурация применяется для обеспечения работы конечной точки через протокол REST;
  8. WebScriptEndpoint – определяет стандартную конечную точку, использующую привязку webHttpBinding и с опцией enableWebScript, установленной в значение true. Данная конфигурация используется в сервисах, функции которых должны быть доступны через AJAX-запросы из веб-страниц;
  9. WorkflowControlEndpoint – определяет стандартную конечную точку для управления выполнением экземпляров рабочего процесса (создание, выполнение, приостановка, завершение и т. д.).

Эти конечные точки отражают наиболее часто используемые сценарии WCF. Поскольку они определены в файле machine.config, вы можете использовать их в своих приложениях, без каких-либо дополнительных действий. Теперь вам не нужно указывать Address, Binding и Contract при использовании стандартных точек. Однако, некоторые конфигурации из списка выше, все же требуют указания контракта или привязки.

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

<configuration>  
  <system.serviceModel>   
    <services>
      <service name="WCFMultiContract.Service" behaviorConfiguration="defaultBehavior">       
        <endpoint kind="webHttpEndpoint" contract="WCFMultiContract.ISimpleCalculator" />        
      </service>
    </services>
  </system.serviceModel>  
</configuration>

Таким образом мы видим насколько уменьшился объем конфигурационных данных для нашего сервиса. Однако это еще не все, если нас скажем не устраивает какой-то параметр в стандартной конфигурации, то мы можем с легкостью немного изменить стандартный шаблон конечной точки, как показано ниже:

<configuration>  
  <system.serviceModel>   
    <services>
      <service name="WCFMultiContract.Service" behaviorConfiguration="defaultBehavior">       
        <endpoint kind="webHttpEndpoint" endpointConfiguration="TransferConfig" 
                  contract="WCFMultiContract.ISimpleCalculator" />        
      </service>
    </services>
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="TransferConfig" transferMode="Buffered"></standardEndpoint>
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>  
</configuration>

В приведенном выше участке кода в разделе standardEndpoints мы переопределили конфигурацию стандартной конечной точки webHttpEndpoint, а точнее установили значение свойства transferMode в значение Buffered. А также связали эту расширенную конфигурацию с нашей конечной точкой при помощи атрибута endpointConfiguration.

 

Endpoint Behavior

Инфраструктура WCF построена таким образом, что для каждого ее элемента можно настраивать поведение(Behavior), например ServiceBehavior, ContractBehavior, OperationBehavior, ChannelBehavior и тд. Не являются исключением и Endpoints. В следующем коде приведен пример задания параметров поведения для конечной точки:

  1: <configuration>  
  2:   <system.serviceModel>
  3:     <behaviors>
  4:       <endpointBehaviors>
  5:         <behavior name="myEndpointBehavior">
  6:           <callbackDebug includeExceptionDetailInFaults="true" />
  7:           <callbackTimeouts transactionTimeout="00:00:00" />
  8:           <clientCredentials type="" supportInteractive="true" />
  9:           <enableWebScript />
 10:           <endpointDiscovery enabled="true" />
 11:           <dispatcherSynchronization asynchronousSendEnabled="true" maxPendingReceives="1000" />
 12:           <clientVia viaUri="" />
 13:           <dataContractSerializer ignoreExtensionDataObject="true" maxItemsInObjectGraph="" />
 14:           <soapProcessing processMessages="true"/>
 15:           <synchronousReceive />
 16:           <transactedBatching maxBatchSize=""/>
 17:           <webHttp automaticFormatSelectionEnabled="true" 
 18:                    defaultOutgoingResponseFormat="Json" helpEnabled="true" />
 19:         </behavior>
 20:       </endpointBehaviors>
 21:     </behaviors>
 22:     <services>
 23:       <service name="WCFMultiContract.Service" >               
 24:         <endpoint address="simple" binding="wsHttpBinding" behaviorConfiguration="myEndpointBehavior" 
 25:                   contract="WCFMultiContract.ISimpleCalculator" />        
 26:       </service>
 27:     </services>
 28:   </system.serviceModel>  
 29: </configuration>

В строках 5-19 объявлена конфигурация поведения конечной точки. В строке 24 эта конфигурация применяется к конечной точке с адресом “simple”. Рассмотрим параметры конфигурации, приведенные выше:

  1. <callbackDebug> – при установке единственного свойства includeExceptionDetailInFaults в значение true, все возникающие ошибки при выполнении операций будут записываться в передаваемое сообщение. Используется с целью проведения отладки сервиса;
  2. <callbackTimeouts> – свойство transactionTimeout устанавливает значение типа TimeSpan, которое указывает максимальное время, через которое транзакция должна завершиться до автоматического ее прерывания. По умолчанию установлено значение “00:00:00”, то есть время выполнения транзакций не ограничено;
  3. <clientCredentials> – раздел определяет параметры аунтефикации клиентов на сервисе. Свойство type определяет тип передаваемой информации и зависит от выбранного метода аунтефикации. Оно может принимать такие значения как ”Windows”, “Certificate” и др. Свойство supportInteractive определяет может ли клиент менять аунтефикационные данные в realtime режиме, по умолчанию установлено в true;
  4. <enableWebScript> – при установке включает для конечной точки возможность выполнять AJAX-запросы с веб-страниц. Данное свойство можно использовать только для конечных точек, использующих привязки(binding) webHttpBinding или webMessageEncoding;
  5. <endpointDiscovery> – устанавливает параметры управления метаданными конечной точки. Свойство enabled определяет доступны ли метаданные текущей конечной точки, по умолчанию установлено в false;
  6. <dispatcherSynchronization> – при установке свойства asynchronousSendEnabled в true. конечная точка получает разрешение отправлять ответы сервиса асинхронно. Свойство maxPendingReceives определяет максимальное количество одновременных сообщений в канале;
  7. <synchronousReceive> – при установке заставляет сервис работать в синхронном режиме приема сообщений;
  8. <clientVia> – свойство viaUri задает маршрут, по которому должно быть передано сообщение;
  9. <dataContractSerializer> – данный элемент определяет поведение конечной точки при сериализации и десериализации сообщений. При установке свойства ignoreExtensionDataObject в значение true, все данные хранящиеся в поле типа ExtensionDataObject при сериализации/десериализации игнорируются. Свойство maxItemsInObjectGraph определяет максимальное количество элементов, которые необходимо сериализовать/десериализовать в одном сообщении;
  10. <soapProcessing> – определяет поведение конечной точки клиента, используемое для упаковки сообщений между различными типами привязок и версиями сообщения;
  11. <transactedBatching> – указывает, поддерживается ли объединение транзакций для операций получения. Свойство maxBatchSize - целое число, указывающее максимальное число операций получения, которые могут быть объединены в одну транзакцию;
  12. <webHttp> – включение этой конфигурации обеспечивает работу конечной точке в формате REST. Свойство defaultOutgoingResponseFormat определяет формат исходящих сообщений сервиса. Свойство helpEnabled указывает, включена ли страница справки HTTP для конечной точки. Свойство automaticFormatSelectionEnabled указывает, включен ли автоматический выбор формата сообщений.

Мы кратко рассмотрели элементы конфигурации поведения конечной точки первого уровня. Конечно, это не все возможные настройки и некоторые секции, из приведенных выше, имеют вложенные элементы конфигурации. Например, секция clientCredentials, но поскольку тема безопасности WCF сервисов выходит далеко за рамки этой статьи, мы ограничились лишь кратким ее описанием.

 

Выводы

В этой статье мы рассмотрели один из основополагающих аспектов инфраструктуры Windows Communication Foundation – Endpoints или конечные точки. Мы познакомились с новыми особенностями их создания и конфигурирования на платформе .NET Framework 4, а также разобрались с многочисленными свойствами управления поведением конечных точек. Приятной работы с технологией WCF!

WCF – это просто: Продвинутое управление контрактами данных

Разработчик создает WCF сервис, запускает его и передает своим клиентам контракт операций сервиса и контракты данных. Клиент успешно взаимодействует с сервисом и все довольны. Это идеальный вариант работы сервис-ориентированной системы. Однако, как показывает практика, во время работы сервиса бывают ситуации, когда необходимо внести изменения в контракты данных и при этом передать новые контракты клиентам уже возможности нет. К счастью, разработчики технологии WCF относятся с пониманием к этой проблеме, поэтому механизм контрактов данных WCF частично позволяет совершать подобные манипуляции.

Сегодня мы поговорим о том, как продолжать развитие своего сервиса, при этом сохраняя его работоспособность с “устаревшими” клиентами. В рамках этой темы мы рассмотрим следующие вопросы:

  1. Что такое эквивалентность контрактов данных;
  2. Управление версиями контрактов данных;
  3. Как создать контракт данных, который будет совместим с любыми будущими изменениями.

Вперед!

Эквивалентность контрактов данных

Под понятием эквивалентные контракты данных мы будем понимать классы-контракты данных,  которые имеют различный синтаксис объявления, но в результате процесса сериализации превращаются в одинаковый xml-объект. Рассмотри пример. Следующие два контракта будут эквивалентными:

1:  [DataContract]                          2:  [DataContract]
    public class Customer                       public class Customer 
    {                                           {        
        [DataMember]                                [DataMember(Name="user_name")]
        public string user_name;                    public string str;
    }                                           }      

И хотя во втором контракте поле имеет другое имя, мы явно указали в атрибуте DataMember механизму сериализации WCF под каким именем необходимо сериализовать данный член контракта данных.

Также важное значение при определении эквивалентности контрактов данных является порядок сериализации членов класса. По умолчанию все члены сериализуются в алфавитном порядке. Однако мы можем явно указать для каждого поля его место в сериализованном объекте. Рассмотрим следующий пример:

1:  [DataContract]                     2:  [DataContract]                     3:  [DataContract]
    public class Customer                  public class Customer                  public class Customer
    {                                      {                                      {
        [DataMember]                           [DataMember]                           [DataMember(Order=2)]
        public int Age;                        public string LastName;                public string FirstName;

        [DataMember]                           [DataMember]                           [DataMember(Order=1)]
        public string FirstName;               public string FirstName;               public int Age;

        [DataMember]                           [DataMember]                           [DataMember(Order=3)]
        public string LastName;                public int Age;                        public string LastName;
    }                                      }                                      }

Все три класса представляют эквивалентные контракты данных и их поля будут сериализоваться в одинаковой последовательности. В первом случае порядок сериализации будет таким же как и порядок объявления полей в классе. Во втором случае поля класса вначале будут отсортированы по алфавиту, а затем сериализованы. В третьем случае мы явно указываем при помощи свойства Order порядок сериализации полей.

Определение эквивалентности контрактов данных, которые являются частью цепочки наследования. Рассмотрим следующий пример:

1:  [DataContract]                                  2:  [DataContract]
    public class Customer                               public class Person
    {                                                   {
        [DataMember]                                        [DataMember]
        public string UserName;                             public string UserName;
                                                        }
        [DataMember]
        public string AccountNumber;                    [DataContract]
                                                        public class Customer : Person
        [DataMember]                                    {
        public decimal Balance;                             [DataMember]
                                                            public string AccountNumber;
        [DataMember]                                        
        public DateTime LastOperationDate;                  [DataMember]
    }                                                       public decimal Balance;
                                                            
                                                            [DataMember]
                                                            public DateTime LastOperationDate;
                                                        }

После сериализации оба контракта будут иметь одинаковые поля, однако они не будут эквиваленты, так как будет отличаться порядок следования этих полей в сериализованном объекте. В первом случае поля класса будет следовать по алфавиту: AccountNumber – Balance – LastOperationDate – UserName.

Во втором же случае классы в цепочке наследования сериализуются в конечный объект по очереди. То есть сначала будут отсортированы поля класса-предка Person и записаны в конечный объект, а затем будут отсортированы и сериализованы поля класса-потомка Customer. В результате получится следующая последовательность: UserName – AccountNumber – Balance – LastOperationDate.

Допустим нам необходимо сделать эти два контракта эквивалентными. Со вторым мы уже ничего не сможем сделать, поэтому будет подстраивать первый контракт для совместимости со вторым. Внесем в него следующие изменения:

    [DataContract]                                  
    public class Customer                              
    {                                                   
        [DataMember(Order=1)]                                       
        public string UserName;                           
                                                      
        [DataMember(Order=2)]
        public string AccountNumber;                   
                                                       
        [DataMember(Order=2)]                                   
        public decimal Balance;                            
                                                           
        [DataMember(Order=2)]                                        
        public DateTime LastOperationDate;                  
    }  

Здесь мы указали очередность сериализации полей в первом контракте. Сначала будет сериализовано поле UserName, а затем остальные поля будут отсортированы по алфавиту и сериализованы в конечный объект. Тем самым мы получим одинаковый порядок сериализации полей: UserName – AccountNumber – Balance – LastOperationDate. Теперь наши контракты являются эквивалентными.

Говоря о наследовании в контрактах данных, необходимо помнить, что нельзя отправить класс-предок, когда конечная точка ожидает класс-потомок – так это противоречит принципам объектно-ориентированного программирования. Обратное возможно, то есть вы можете отправить класс-потомок, когда ожидается класс-предок, однако только в том случае, если сервис знает о возможных типах потомков, которые может принимать определенное поле контракта данных. Сообщить ему об этом можно установив классу-контракту данных один или несколько атрибутов  KnownType. (Подробнее мы рассматривали аспекты применения данного атрибута в предыдущей статье - WCF – это просто: Контракты данных).

 

Управление версиями контрактов данных

Одним из преимуществ технологии WCF, как упоминалось выше, является возможность развития контрактов данных, без потери обратной совместимости. То есть, клиент, использующий старую версию контракта данных, может взаимодействовать с сервисом, работающим с  новой версией контракта данных и наоборот. Однако, при обеспечении такой совместимости накладываются строгие правила на изменения, которые может вносить разработчик в новый контракт данных. В частности, все изменения делятся на 2 вида: некритические изменения – изменения, после которых обеспечивается обратная совместимость и критические, после которых сервис уже не будет способен работать со старыми(не обновлёнными) клиентами.

Разберемся какие изменения относятся к разряду некритических. Это те изменения, при внесении которых, в большинстве случаев, будет сохранена обратная совместимость:

1) Добавление и удаление членов контракта данных. Рассмотри два следующих контракта:

    [DataContract]                              [DataContract]
    public class Customer                       public class Customer 
    {                                           {        
        [DataMember]                                [DataMember]
        public string FirstName;                    public string FirstName;
    }                                                       
                                                    [DataMember]
                                                    public string SecondName;
                                                }

Допустим, что первый контракт используется клиентом, а второй – сервисом. Когда данные приходят из сервиса клиент не находит в своем контракте поля SecondName, поэтому просто игнорирует его данные. Когда же клиент отправляет сервису сообщение без этого поля, то сервис подставляет для него значение по умолчанию. В большинстве случаев эта схема работы всех устраивает, однако предусмотрена возможность ее отключения. Если в контракте данных сервиса в атрибуте DataMember, которым отмечено поле SecondName, указать свойство IsRequired=true, следующим образом:

    [DataMember(IsRequired=true)]
    public string SecondName;

то, при получении сообщения от клиента без этого поля, сервис не подставит в него значение по умолчанию, а сгенерирует исключение.

2) Изменение имени членов контракта данных. Для сервиса не имеет значения как называется поле в вашем классе, для него важно – под каким именем это поле буде сериализоваться при отправке сообщения. По умолчанию, имя в сериализованном объекте – это имя поля в классе, однако его можно переопределить, установив свойство Name в атрибуте DataMember. Таким образом, следующие два контракта будут абсолютно одинаково сериализоваться и, как следствие, будут полностью совместимы:

    [DataContract]                              [DataContract]
    public class Customer                       public class Customer 
    {                                           {        
        [DataMember]                                [DataMember(Name="FirstName"]
        public string FirstName;                    public string myname;
    }                                           }

Аналогично это работает и для имен классов:

    [DataContract]                              [DataContract(Name="Customer")]
    public class Customer                       public class MyClass 
    {                                           {        
        [DataMember]                                [DataMember]
        public string FirstName;                    public string FirstName;
    }                                           }

Также не стоит забывать, что осуществляется строгая проверка регистра в названиях полей, названиях контрактов и пространствах имен. И даже при несоответствии одной буквы в названии класса или поля, совместимость контрактов будет утеряна.

3) Реализация контрактом данных интерфейса IExtensibleDataObject является некритическим изменением. Подробнее функционал этого интерфейса мы рассмотрим далее в этой статье.

Теперь рассмотрим критические изменения в контрактах данных, внесение которых приведет к потере совместимости со старыми версиями:

1) Изменение свойств Name и Namespace атрибута DataContract, а также свойства Name в атрибуте DataMember;

2) Изменение порядка сериализации членов контракта данных, используя свойство Order атрибута DataMember;

3) Переименование  класса-контракта данных или его членов(без перекрытия свойством Name);

4) Изменение типа данных полей в классе-контракте данных;

5) Добавление или удаление члена перечисления (enum), которое используется в контракте данных.

Выше мы рассмотрели виды изменений в контрактах данных и степень их влияния на совместимость контрактов с предыдущими версиями. И хотя, механизм Windows Communication Foundation частично позволяет совершать такие манипуляции с контрактами данных, к ним необходимо прибегать только в крайнем случае. Наилучшей практикой в таких случаях считается не внесение изменений в контракт данных, а создание нового контракта данных и новой конечной точки для него. То есть, следует оставить старый функционал и контракты сервиса, для того чтобы обслуживать клиентов со старой версией контракта данных и создать новую конечную точку на сервисе, с которой будут взаимодействовать новые клиенты с последней версией контрактов.

 

Создание контракта данных, который будет совместим с любыми будущими изменениями

Этот заголовок звучит почти как мистический, однако, это возможно и сейчас мы разберемся как. Чтобы наш контракт данных поддерживал режим полной совместимости он должен наследовать специальный интерфейс – IExtensibleDataObject. Рассмотрим следующий, совершенно обыкновенный контракт:

    [DataContract]
    public class CustomType
    {
        [DataMember]
        string stringValue;
    }

Теперь попробуем применить к нему режим полной совместимости:

    [DataContract]
    public class CustomType : IExtensibleDataObject
    {
        [DataMember]
        string stringValue;

        private ExtensionDataObject _data;
        public ExtensionDataObject ExtensionData
        {
            get { return _data;  }
            set { _data = value; }
        }
    }

В нашем классе появились поле _data и свойство ExtensionData, предоставляющее доступ к полю _data. Именно это поле будет служить хранилищем для всех неизвестных данных. То есть, если “устаревший” клиент получит сообщение с новыми полями в контракте данных, то он запишет все, неизвестные в его версии контракта данных, поля в объект типа ExtensionDataObject.  При возвращении сообщения обратно на сервис эти данные будут успешно десериализированы в свои изначальные поля. Тем самым, режим полной совместимости гарантирует, что никакие данные не будут утеряны.

Также, существует возможность отключения режима полной совместимости, для этого необходимо установить следующее свойство в атрибуте ServiceBehavior:

      [ServiceBehavior(IgnoreExtensionDataObject=true)]

Либо, непосредственно в файле конфигурации, в разделе поведения сервиса:

    <behavior>
        <dataContractSerializer ignoreExtensionDataObject="true"/>
    </behavior>

 

Выводы

В этой статье статье мы рассмотрели процедуру управления версиями контрактов данных, разобрались как вносить изменения в контракты данных, обеспечивая при этом их совместимость с более ранними версиями. Также узнали об критических и не критических изменениях в контрактах данных и изучили основные правила, применяемые к ним. А также научились создавать универсальные контракты данных, которые будут совместимы со всеми будущими изменениями.

WCF – это просто: Контракты данных

Сегодня мы познакомимся с одним из основных понятий WCF – контрактами данных. С контрактами данных безусловно сталкивался любой разработчик WCF сервисов, который решил написать что-нибудь сложнее калькулятора :).

В основе работы работы WCF сервисов лежит протокол SOAP (Simple Object Access Protocol), который работает поверх протокола HTTP и предполагает передачу сообщений по сети в формате XML. Следовательно сервис WCF при взаимодействии с клиентом должен сначала сериализовать данные в XML, а затем передать их клиенту через SOAP, клиент в свою очередь должен десериализовать полученное сообщение. Соответственно, все типы данных, которые мы используем в контрактах нашего сервиса должны быть сериализуемыми. Рассмотрим следующий пример, контракт сервиса:

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        int Sum(int firstNumber, int secondNumber);
        [OperationContract]
        double Divide(int firstNumber, int secondNumber);
    }

В данном случае у механизма WCF не возникнет никаких проблем с сериализацией, поскольку оба метода нашего сервиса используют только элементарные типы данных: int и double. Однако на практике, настолько простые сервисы практически не встречаются, в большинстве случаев между клиентом и сервисом необходимо передавать сложные, комплексные типы данных, например:

    public class MyComplexNumber
    {
        public int x;
        public int y;
        public int i;
        public string notes;
    }
    
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        MyComplexNumber Sum(MyComplexNumber firstNumber, MyComplexNumber secondNumber);
        [OperationContract]
        MyComplexNumber Divide(MyComplexNumber firstNumber, MyComplexNumber secondNumber);
    }

Здесь функции сервиса оперируют нашим, производным, типом данных. Соответственно, класс MyComplexNumber должен корректно сериализоваться в формат XML. WCF предоставляет нам достаточно гибкий механизм управления этим процессом. Мы можем настроить сериализацию нашего класс двумя следующими способами:

1) Использовать стандартный механизм – DataContractSerializer. В этом случае мы можем оставить код, приведенный выше, как есть и наш сервис самостоятельно его сериализует. При этом будут сериализованы все поля, объявленные с модификатором доступа public. А также все публичные свойства, имеющие открытые методы get и set. Мы также можем указать, какие поля мы не хотим сериализовать – их необходимо пометить атрибутом IgnoreDataMemberAttribute, как показано ниже:

    public class MyComplexNumber
    {
        public int x;
        public int y;
        public int i;

        [IgnoreDataMember]
        public string notes;
    }

Атрибут IgnoreDataMemberAttribute находится в сборке System.Runtime.Serialization, которую необходимо дополнительно подключить к проекту.

2) Использовать атрибуты DataContract и DataMember. В этом случае мы говорим механизму WCF, что мы сами, явно, укажем все поля и свойства, которые необходимо сериализовать. Напишем код, который по результату будем аналогичен предыдущему:

    [DataContract]
    public class MyComplexNumber
    {
        [DataMember]
        public int x;
        [DataMember]
        public int y;
        [DataMember]
        public int i;
        
        public string notes;
    }

Атрибутом DataContract помечается весь класс, а атрибутом DataMember помечаются поля и свойства, которые необходимо будет сериализовать.

Нужно сказать несколько слов о взаимодействии этих двух способов. Вы, конечно, можете использовать атрибут DataMember в классе, не отмеченном атрибутом DataContract или ставить атрибут IgnoreDataMember в классе, с атрибутом DataContract. Эти действия не вызовут ошибку компиляции или выполнения, однако желаемого эффекта вы также не получите, поскольку данные атрибуты будут игнорироваться.

 

         Сериализация перечислений

         Итак, мы рассмотрели аспекты связанные с сериализацией полей и свойств класса, представляющие простые типы данных. Теперь рассмотрим ситуацию, когда тип данных одного из полей класса – созданное нами перечисление (enum). Для перечислений в WCF также предусмотрена система сериализации, очень похожая на ту, что мы рассмотрели выше. Приведем следующий пример:

    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        MyInformation GetInformation();        
    }
    
    public class MyInformation
    {
        public string Message;
        public string Author;
        public ProtectionLevelEnum protectionLevel;
    }

    public enum ProtectionLevelEnum
    {
        High,
        Medium,
        Low,
        Custom
    };

Контрактом данных является класс MyInformation, одно из полей которого имеет тип перечисление.  Чтобы перечисление ProtectionLevelEnum могли использовать и клиент и сервис – его также необходимо сериализовать и передавать в сообщении. Настроить его сериализацию мы, также, можем двумя способами:

1) Используя атрибуты DataContract и EnumMember:

    [DataContract]
    public enum ProtectionLevelEnum
    {
        [EnumMember]
        High,
        [EnumMember]
        Medium,
        [EnumMember]
        Low,
        Custom
    };

При этом будут сериализованы все члены перечисления, отмеченные атрибутом EnumMember.

2) Использовать стандартный механизм сериализации – при этом все элементы перечисления будут сериализоваться, так как-будто они отмечены атрибутом EnumMember, за исключением тех значений, для которых мы установили атрибут NonSerialized:

    public enum ProtectionLevelEnum
    {        
        High,        
        Medium,        
        Low,

        [NonSerialized]
        Custom
    };

 

     Сериализация коллекций

     Следующий важный тип данных, который может быть членом контракта данных – это коллекции. Рассмотрим особенности сериализации этого типа:

1) Взаимозаменяемые коллекции. Все коллекции-списки имеют одинаковый контракт данных, следовательно следующие три контракта данных будут полностью эквивалентны:

        [DataContract(Name="RootElement")]
        public class MyData
        {
            [DataMember]
            string[] mylist;
        }

        [DataContract(Name = "RootElement")]
        public class MyData
        {
            [DataMember]
            List<string> mylist;
        }

        [DataContract(Name = "RootElement")]
        public class MyData
        {
            [DataMember]
            Collection<string> mylist;
        } 
Все три контракта в результате сериализуются в следующий XML код:
<RootElement>
  <mylist>
     <string>...</string> 
     <string>...</string> 
     <string>...</string> 
     ...
  </mylist>
</RootElement>

Как вы заметили свойство Name, которое мы устанавливаем в атрибуте DataContract определяет название корневого элемента XML кода, аналогично его можно использовать и для атрибута DataMember.

2) В качестве параметров функций сервиса не могут выступать многомерные массивы, следующий код вызовет исключение при запуске сервиса:

        [ServiceContract]
        public interface MyService
        {
            double GetElementsSum(double[,] mas);
        }

Однако поддерживаться вложенные коллекции и вышеприведенный код можно переписать следующим образом:

        [ServiceContract]
        public interface MyService
        {
            double GetElementsSum(List<double[]> mas);
        }

3) Настройка типов коллекции. Данная настройка применяется с помощью атрибута CollectionDataContractAttribute, рассмотрим следующий пример:

        public class MyCollection : Collection<string> { }

Если теперь использовать MyCollection в качестве поля контракта данных, то коллекция сериализуется следующим образом:

<ArrayOfstring>
   <string>...</string>
   <string>...</string>
   <string>...</string>
   ...
</ArrayOfstring>
Имя по умолчанию контрактов данных коллекций списков, если оно не переопределено, является строкой "ArrayOf…". Если же пометить нашу коллекцию атрибутом CollectionDataContractAttribute:
        [CollectionDataContract]
        public class MyCollection : Collection<string> { }

то теперь имя и пространство имен контракта данных коллекции зависят от типа самой коллекции и мы получим примерно следующий XML код:

<MyCollection>

    <string>...</string>

    <string>...</string>

    <string>...</string>

    ...

</MyCollection>

Также мы можем в атрибуте CollectionDataContractAttribute явно указать названия элементов в XML дереве, это делается с помощью атрибутов Name и ItemName:

        [CollectionDataContract(Name="mysongs", ItemName="song")]
        public class MyCollection : Collection<string> { }

XML код в результате будет следующим:

<mysongs>
   <song>...</song>
   <song>...</song>
   <song>...</song>
   ...
</mysongs>

3) Настройка словарей. Мы также можем управлять параметрами сериализации коллекций-словарей, устанавливать имена для ключей и значений словаря можно при помощи того же атрибута CollectionDataContractAttribute и параметров KeyName и ValueName. Рассмотрим пример:

        [CollectionDataContract(Name="mysongs", ItemName="song", KeyName="id", ValueName="text")]
        public class MyDictionary : Dictionary<int, string> { }

результатом сериализации будет код следующий XML код:

<mysongs>
   <song>
      <id>1</id>
      <text>abc</text>
   </song>
   <song>
      <id>2</id>
      <text>def</text>
   </song>
</mysongs>

4) Также рассмотрим некоторые варианты использования атрибута CollectionDataContractAttribute с коллекциями в контрактах данных, приводящие к ошибке InvalidDataContractException при запуске сервиса:

  • Применение атрибута DataContractAttribute к типу, который был помечен атрибутом CollectionDataContractAttribute, или к одному из наследованных от него типов;
  • Применение атрибута CollectionDataContractAttribute к типу, не являющемуся коллекцией;
  • Попытка задать параметры KeyName или ValueName атрибуту CollectionDataContractAttribute, применяемому к типу, не являющемуся словарем.

     

    Значения элементов контракта данных по умолчанию

    Один из способов уменьшения размеров контракта данных – это исключение из него полей имеющих значение по умолчанию(например null), то есть, если поле контракта данных при передаче через сервис будет иметь  значение по умолчанию, то оно не будет включено в сериализованное сообщение. Для этого необходимо установить свойство EmitDefaultValue  атрибута DataMember в значение false.

    Также в атрибуте DataMember есть свойство IsRequired, при установке которого в значение true, мы говорим механизму WCF, что это поле должно обязательно присутствовать в контракте данных. Вполне логично. что одновременное применение свойств EmitDefaultValue=false и IsRequired=true невозможно, так как они имеют противоположный эффект.

     

    Известные типы контрактов данных

       Обычно при передаче данных между клиентом и сервисом обе стороны совместно используют типы данных, относящиеся к контрактам данным(например когда клиент и сервис находятся в одном решении). Однако бывают ситуации, когда это не так. Рассмотрим следующий код объявления сервиса:

        [DataContract]
        public class Shape { }
    
        [DataContract]
        public class Circle : Shape { }
    
        [DataContract]
        public class Triangle : Shape { }
    
        [DataContract]
        public class FigureData
        {     
          [DataMember] 
            public Shape figure;
        }
    
        [ServiceContract]
        public interface FigureOperations
        {
            FigureData GetFigure();
        }

    Контрактом данных тут будет выступать класс FigureData. В случае, если реализация сервиса будет следующей, наш код отлично сработает:

        public class MyService : FigureOperations
        {
            public FigureData GetFigure()
            {
                return new FigureData() { figure = new Shape() };
            }
        }
    

    Однако, при следующей реализации возникнет ошибка, при передаче данных:

        public class MyService : FigureOperations
        {
            public FigureData GetFigure()
            {
                return new FigureData() { figure = new Circle() };
            }
        }
    

    Присвоение полю figure значения типа Circle совершенно корректно с точки зрения полиморфизма в .NET, однако недопустимо в WCF. Дело в том, что клиент сервиса знает лишь о тех типа данных, которые явно указаны в контракте данных(в нашем случае – о классе Shape). Чтобы это исправить необходимо использовать атрибут KnownTypeAttribute следующим образом:

        [DataContract]
        [KnownType(typeof(Circle))]
        [KnownType(typeof(Triangle))]
        public class FigureData
        {
            public Shape figure;
        }

    Тем самым мы указываем значения каких типов могут принимать поля нашего контракта данных.

     

    Наследование и контракты данных

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

            public class BaseData
            {
                public string author;
            }
    
            [DataContract]
            public class MyData : BaseData
            {
                [DataMember]
                public int id;
                [DataMember]
                public string text;
            }
    

    Чтобы это сработало необходимо пометить класс BaseData атрибутом DataContract, как показано далее:

        [DataContract]
        public class BaseData
        {
            public string author;
        }
    
        [DataContract]
        public class MyData : BaseData
        {
            [DataMember]
            public int id;
            [DataMember]
            public string text;
        }
    

     

    Выводы

        В этой статье мы рассмотрели основные понятия и примеры создания разнообразных контрактов данных, рассмотрели аспекты полного управления над процессом сериализации контрактов данных и изучили основные ошибки, которые можно допустить при их написании. В будущем это нам поможет быстро и правильно создавать сложные сервисы на основе технологии Windows Communication Foundation. Приятной работы с  технологией WCF!

  • WCF – это просто: создание сервиса, который реализует несколько контрактов

    В этой статье мы рассмотрим, каким образом создать и сконфигурировать сервис, который реализует и предоставляет своим клиентам несколько контрактов.

    В ситуации, когда WCF сервис инкапсулирует в себе значительное количество логики и предоставляет клиентам большое количество функций, становится необходимо разделить функционал этого сервиса на несколько менее объемных интерфейсов, при этом необходимо, чтобы все они продолжали предоставляться одним и тем же сервисом. Такой подход позволяет создать хорошо структурированную архитектуру на стороне сервиса, что в свою очередь обеспечивает возможность удобного обслуживания и поддержки WCF сервиса или системы WCF сервисов.

    WCF сервис способен реализовывать неограниченное количество контрактов, однако для каждого контракта должна быть создана своя конечная точка, которая будет располагаться(или в терминологии WCF - слушать) свой уникальный url-адрес.

    Для примера создадим 2 контракта:

    1) ISimpleCalculator, который будет выполнять базовые математические функции – сложение, вычитание, умножение и деление:

        [ServiceContract]
        public interface ISimpleCalculator
        {
            [OperationContract]
            double Plus(double firstNumber, double secondNumber);
            [OperationContract]
            double Minus(double firstNumber, double secondNumber);
            [OperationContract]
            double Multiply(double firstNumber, double secondNumber);
            [OperationContract]
            double Divide(double firstNumber, double secondNumber);
        }
    

    2) IAdvancedCalculator, который реализует более сложные математические функции – извлечение квадратного корня, возведение в степень и нахождение синуса и косинуса числа:

        [ServiceContract]
        public interface IAdvancedCalculator
        {
            [OperationContract]
            double SquareRoot(double number);
            [OperationContract]
            double Power(double number, double power);
            [OperationContract]
            double Cos(double number);
            [OperationContract]
            double Sin(double number);
        }
    Теперь создадим сервис Service.cs (класс, который наследует два интерфейса: ISimpleCalculator и IAdvancedCalculator )
    , где будут реализованы оба контракта:
        public class Service : ISimpleCalculator, IAdvancedCalculator
        {
            public double Plus(double firstNumber, double secondNumber)
            {
                return firstNumber + secondNumber;
            }
            public double Minus(double firstNumber, double secondNumber)
            {
                return firstNumber - secondNumber;
            }
            public double Multiply(double firstNumber, double secondNumber)
            {
                return firstNumber * secondNumber;
            }
            public double Divide(double firstNumber, double secondNumber)
            {
                return firstNumber / secondNumber;
            }
            public double SquareRoot(double number)
            {
                return Math.Sqrt(number);
            }
            public double Power(double number, double power)
            {
                return Math.Pow(number, power);
            }
            public double Cos(double number)
            {
                return Math.Cos(number);
            }
            public double Sin(double number)
            {
                return Math.Sin(number);
            }
        }
    
    Реализация Функций сервиса завершена, теперь перейдем к самому интересному и важному – конфигурации конечных точек сервиса. 
    Все настройки сервиса находятся в файле Web.config в разделе <system.serviceModel>:
    <?xml version="1.0"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
      <system.serviceModel>
        <behaviors>
          <serviceBehaviors>
            <behavior name="defaultBehavior">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="False" />
            </behavior>
          </serviceBehaviors>      
        </behaviors>
        <bindings>
          <basicHttpBinding>
            <binding name="defaultBinding" />
          </basicHttpBinding>      
        </bindings>
        <services>
          <service name="WCFMultiContract.Service" behaviorConfiguration="defaultBehavior">
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost/" />
              </baseAddresses>            
            </host>
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
            <endpoint address="simple" binding="wsHttpBinding" bindingConfiguration="" contract="WCFMultiContract.ISimpleCalculator" />
            <endpoint address="advanced" binding="basicHttpBinding" bindingConfiguration="defaultBinding" 
    contract="WCFMultiContract.IAdvancedCalculator" /> </service> </services> </system.serviceModel> <system.webServer> <modules runAllManagedModulesForAllRequests="true"/> </system.webServer> </configuration>

    Мы зарегистрировали три конечные точки для нашего сервиса ( теги <endpoint…/> ), рассмотрим параметры их конфигурации более подробно:

              1) <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> это стандартная конечная точка, которая предоставляет клиентам метаданные сервиса, через нее пользователи узнают какие функции может выполнять наш сервис и какими типами данных он оперирует. Адрес этой точки будет следующим: “http://localhost/service.svc/mex”;

              2) <endpoint address="simple" binding="wsHttpBinding" bindingConfiguration="" contract="WCFMultiContract.ISimpleCalculator" />  через данную точку будут выполняться операции определенные в интерфейсе ISimpleCalculator. Параметр binding определяет тип WCF биндинга (тип привязки), который будет использоваться данной конечной точкой. Для данной точки это – “wsHttpBinding”. Параметр bindingConfiguration оставлен пустым, это значит что будет использоваться стандартная конфигурация WCF для wsHttpBinding. В параметр contract вносится полный путь к контракту(интерфейсу) данной конечной точки. Последний параметр address указывает последнюю часть url-адреса конечной точки. Полный адрес этой точки - http://localhost/service.svc/simple”;

              3) <endpoint address="advanced" binding="basicHttpBinding" bindingConfiguration="defaultBinding" contract="WCFMultiContract.IAdvancedCalculator" /> – последняя конечная точка будет расположена по адресу: “http://localhost/service.svc/advanced” и будет предоставлять функции, определенные интерфейсом IAdvancedCalculator. Данная точка использует другой тип биндинга – “basicHttpBinding”, конфигурация которого определена в разделе <bindings> –> <basicHttpBinding> нашего файла конфигурации Web.config. В данном случае я явно внес конфигурацию basicHttpBinding в конфигурационный файл лишь для примера, поскольку мы не переопределяем стандартные параметры basicHttpBinding, мы могли оставить параметр bindingConfiguration пустым.

    На этом процесс создания и конфигурирования нашего сервиса завершен – перейдем к созданию клиента, который будет использовать наш сервис.

    Первым делом нам необходимо запустить наш сервис. Это можно сделать несколькими способами: запустить ASP.NET приложение либо опубликовать сайт/сервис при помощи IIS.

    Создадим новое консольное приложение и назовем его Client. Теперь необходимо добавить ссылку на наш сервис: Щелкнув правой кнопкой на проекте в Solution Explorer выберем пункт меню Add Service Refference. Далее в диалоговом окне необходимо указать адрес нашего сервиса (адрес файла service.svc). В поле Address необходимо ввести - http://localhost/Service.svc и нажать кнопку “Go”. Если все прошло успешно в списке  Services появится наш сервис. Осталось указать пространство имен (поле Namespace), в котором будут сгенерированы классы, предоставляющие доступ к функциям нашего сервиса. Я назвал пространство имен – MyService. Мы можем убедится, что в нем находятся два автоматически сгенерированных класса: SimpleCalculatorClient и AdvancedCalculatorClient, которые содержат в себе методы двух контрактов нашего сервиса соответственно.

    Ниже приведен код, демонстрирующий вызов методов сервиса клиентом:

    using Client.MyService;
    
    namespace Client
    {
        class Program
        {
            static void Main()
            {
                // Клиент первого контракта:
                using (SimpleCalculatorClient client1 = new SimpleCalculatorClient())
                {
                    client1.Open();  // Открываем соединение с сервисом и вызываем его методы:
                    double plusResult =  client1.Plus(25, 13);
                    Console.WriteLine(String.Format("25 + 13 = {0}", plusResult));
                    double minusResult = client1.Minus(25, 13);
                    Console.WriteLine(String.Format("25 - 13 = {0}", minusResult));
                    double multiplyResult = client1.Multiply(25, 13);
                    Console.WriteLine(String.Format("25 * 13 = {0}", multiplyResult));
                    double divideResult = client1.Divide(25, 13);
                    Console.WriteLine(String.Format("25 / 13 = {0}", divideResult));
                    client1.Close(); // Закрываем соединение с сервисом            
                }
                Console.WriteLine();
    
                // Клиент второго контракта:
                using (AdvancedCalculatorClient client2 = new AdvancedCalculatorClient())
                {
                    client2.Open();  // Открываем соединение с сервисом и вызываем его методы:
                    double squareResult = client2.SquareRoot(25);
                    Console.WriteLine(String.Format("sqrt(25) = {0}", squareResult));
                    double powerResult = client2.Power(4, 2);
                    Console.WriteLine(String.Format("4^2 = {0}", powerResult));
                    double sinResult = client2.Sin(90);
                    Console.WriteLine(String.Format("Sin(90) = {0}", sinResult));
                    double cosResult = client2.Cos(30);
                    Console.WriteLine(String.Format("Cos(30) = {0}", cosResult));
                    client2.Close(); // Закрываем соединение с сервисом
                }
            }
        }
    }
    
    

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

    Приятной работы с сервисами WCF!

    Скачать исходный код приложения вы можете по следующей ссылке: WCFMultiContract.zip

    WCF – это просто: AJAX, JSON и WCF

    Сегодня нам все чаще встречаются веб-приложения использующие Ajax запросы на своих страницах. Это обусловлено такими преимуществами технологии Ajax, как уменьшение трафика, ускорение загрузки страниц и снижение нагрузки на сервер. В этой статье мы рассмотрим самый простой пример взаимодействия веб-страницы на стороне клиента с нашим WCF сервисом путем использования технологии Ajax и формата передачи данных Json.

    Шаг 1. Создадим наш WCF сервис:

    1. Создадим новый проект: ASP.NET Empty Web Application;
    2. Добавим к проекту следующие ссылки;

    - System.ServiceModel
    - System.ServiceModel.Web

       3.   Создадим класс service.cs, в который разместим контракт и функционал сервиса: 

      1: using System.ServiceModel;
    
      2: using System.ServiceModel.Web;
    
      3: 
    
      4: namespace WCFTestWebApp
    
      5: {
    
      6:     [ServiceContract(Namespace = "MyAjaxService")]
    
      7:     public interface ICalculator
    
      8:     {
    
      9:         [WebGet]
    
     10:         double Add(double n1, double n2);        
    
     11:     }
    
     12: 
    
     13:     public class CalculatorService : ICalculator
    
     14:     {
    
     15:         public double Add(double n1, double n2)
    
     16:         {
    
     17:             return n1 + n2;
    
     18:         }       
    
     19:     }
    
     20: }
      4.   Добавим файл сервиса service.svc:
      1: <%@ServiceHost language="C#" Debug="true" Service="WCFTestWebApp.CalculatorService" 
    
      2:                              Factory="System.ServiceModel.Activation.WebScriptServiceHostFactory" %>
    На этом настройка сервиса завершена, что самое приятное - нет необходимости вручную вносить конфигурацию сервиса в web.config файл. Теперь займемся созданием веб-страницы, которая будет использовать наш сервис.

    Шаг 2. Работа с сервисом на веб-странице:

    Мы рассмотрим несколько вариантов вызова нашего сервиса:

    • вызов из веб-страницы ASP.NET, используя компоненты ASP и Microsoft Ajax;
    • работа с сервисом на html-странице при помощи библиотеки jQuery.
    1. Работа из ASP.NET. Стоит отметить, что разработчики позаботились о том чтобы сделать работу с сервисом из javascript кода страницы максимально простым и похожим на обычную работу с сервисами WCF. Добавим к проекту страницу ASP.NET и внесем на нее следующий код:
      1: <body>
    
      2:     <h1>Simple AJAX Service Client Page</h1>
    
      3:     <p>First Number:<input type="text" id="num1" /></p>
    
      4:     <p>Second Number:<input type="text" id="num2" /></p>
    
      5:     <input id="btnAdd" type="button" onclick="return makeCall();" value="Add" />   
    
      6:     <p>Result:<input type="text" id="result" /></p>
    
      7:     <form id="mathForm" action="" runat="server">
    
      8:         <asp:ScriptManager ID="ScriptManager" runat="server">
    
      9:             <Services>
    
     10:                 <asp:ServiceReference Path="/service.svc" />
    
     11:             </Services>
    
     12:         </asp:ScriptManager>
    
     13:     </form>
    
     14:     <script type="text/javascript">
    
     15:         function makeCall() {
    
     16:             var n1 = document.getElementById("num1").value;
    
     17:             var n2 = document.getElementById("num2").value;
    
     18:             var proxy = new MyAjaxService.ICalculator();
    
     19:             proxy.Add(parseFloat(n1), parseFloat(n2), onSuccess, null, null);
    
     20:         }
    
     21:         function onSuccess(result) {
    
     22:             document.getElementById("result").value = result;
    
     23:         }
    
     24:     </script>
    
     25: </body>
    Давайте разберем этот код более подробно. 

    В строках 2-6 расположены 2 поля для входных данных, поле для вывода результата и кнопка, при нажатии на которую вызывается javascript-метод makeCall().

    Далее в форме находится элемент управления ASP.NET – ScriptManager, в котором зарегистрирована ссылка на наш сервис. После запуска страницы данный элемент управления подтянет данные из сервиса, необходимые для создания proxy-объекта, с помощью которого мы будем обращаться к методам нашего сервиса.

    Функция makeCall() извлекает входные данные из полей формы на странице, создает proxy-класс и вызывает метод Add, передавая туда входные данные и название javascript функции(onSuccess), которая выполнится после получения ответа от сервиса.

    Стоит обратить ваше внимание на необычный синтаксис создания proxy-класса в нашем javascript коде: new MyAjaxService.ICalculator();  -  после ключевого слова new идет пространство имен, которое мы указали в атрибуте сервиса [ServiceContract] (код сервиса строка 6), и далее имя контракта сервиса.

    Приведенный выше код, использует элемент управления ASP.NET Web Forms, однако он будет отлично работать, как в проектах ASP.NET Web Forms, так и в проектах, использующих новейшую технологию ASP.NET MVC Framework.

       2. Вызов сервиса при помощи наиболее популярной javascript библиотеки - jQuery. Добавим в проект новую html-страницу со следующим кодом:

      1: <body>
    
      2:     <h1>Simple AJAX Service Client Page</h1>
    
      3:     <p>First Number:<input type="text" id="num1" /></p>
    
      4:     <p>Second Number:<input type="text" id="num2" /></p>
    
      5:     <input id="btnAdd" type="button" value="Add" />    
    
      6:     <p>Result:<input type="text" id="result" /></p>    
    
      7:     <script src="/jquery-1.4.1.js" type="text/javascript"></script>
    
      8:     <script type="text/javascript">
    
      9:         $(document).ready(function () {
    
     10:             $('#btnAdd').click(function () {
    
     11:                 $.getJSON(
    
     12:                     "/service.svc/Add",
    
     13:                     { n1: $('#num1').val(), n2: $('#num2').val() },
    
     14:                     function (data) {
    
     15:                         $('#result').val(data.d);
    
     16:                     });
    
     17:             });
    
     18:         });
    
     19:     </script>
    
     20: </body>
    Не забудьте добавить в проект файл библиотеки jQuery. В строке 7 эта библиотека добавляется на страницу.

    При нажатии на кнопку вызывается jQuery функция $.getJSON, которая отправляет на сервис данные  и получает ответ в формате JSON. В строке 12 указывается путь к нашему сервису относительно папки веб-приложения и название метода сервиса, который будет вызван. В строке 13 входные данные для отправки на сервис записываются в формате JSON. Последним параметром идет функция, которая выполнится в случае удачного взаимодействия с сервисом и покажет нам наш результат.

    Также можно немного  расширить вышеприведенный код, например добавим туда функцию, которая будет срабатывать в случае какой-либо ошибки во время работы ajax-запроса. Для этого следует использовать другой метод из библиотеки jquery для создания ajax запроса:

    $.ajax({
                    type: "GET",
                    dataType: "json",
                    contentType: "application/json",
                    url: "/service.svc/Add",
                    data: "{ ‘n1’: $('#num1').val(), ‘n2’: $('#num2').val() }”,
                    success: function (data) {
                        $('#result').val(data.d);
                    },
                    error: function () {
                        alert("Error calling the web service.");
                    }
                });

     

    В этой статье мы убедились насколько легко работать с сервисами Windows Communication Foundation на стороне клиента в веб-приложении. Надеюсь, этот пример окажется для вас полезным!

    Код приложения вы можете скачать по следующей ссылке: WCFTestWebApp.zip

    WCF - это просто: быстрая настройка SSL соединения

    Как правило, одним из требований при построении сервис-ориентированной системы является защита передаваемых данных. В этой статье я расскажу как обеспечить для сервиса безопасность на уровне транспорта, настроив его для работы через https протокол. Это будет один из самых простых сценариев, реализуемых при помощи WCF. Мы будем использовать IIS 7 для хостинга нашего сервиса.

    Шаг 1. Создание SSL сертификата. В нашем случае для разработки и тестирования сервиса мы будем использовать самозаверенный сертификат.

    1) Откройте панель управления IIS и выберите пункт меню сертификаты сервера;

    2) Нажмите на ссылку создать самозаверенный сертификат и введите любое имя, например – mycertificate.

    3) Теперь необходимо связать данный сертификат с сайтом, на котором будет расположен наш сервис (в моем случае, это - localhost). Для этого необходимо добавить к сайту новую привязку, установив тип соединения https и выбрать из списка ранее созданный сертификат(см рисунок далее).

    image

    На этом настройки IIS завершены и мы можем перейти непосредственно к разработке сервиса.

    Шаг 2. Создание WCF сервиса.

    Определим простой контракт, который будет выполнять наш сервис и его реализацию:

    namespace WCF_SSLTesting
    {
        [ServiceContract(Namespace = "WCF_SSLTesting")]
        public interface IService
        {
            [OperationContract]
            int Sum(int a, int b);
        }

       public class MyService : IService
        {
            public int Sum(int a, int b)
            {
                return a + b;
            }
        }
    }

    Теперь сконфигурируем наш сервис, установив использование basicHttpBinding и https соединение.

    1) В разделе <bindings> мы регистрируем basicHttpBinding, установив для него режим безопасности на уровне транспорта:

    <bindings>
          <basicHttpBinding>
            <binding name="mybasicHttpBinding">
                <security mode="Transport">
                    <transport clientCredentialType="None" proxyCredentialType="None"/>
                </security>
            </binding>
          </basicHttpBinding>
    </bindings>

    2) Настроим поведение сервиса в разделе <behaviors> - здесь указывается по какому протоколу возможно получить метаданные сервиса. Установим httpGetEnabled в false, а httpsGetEnabled в true:

    <behaviors>
          <serviceBehaviors>
            <behavior name="">
              <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>     
          </serviceBehaviors>
    </behaviors>

    3) Теперь создадим 2 конечные точки для нашего сервиса:

    • basicHttpBinding - основная рабочая точка
    • mexHttpsBinding - точка получения метаданных

    <services>
          <service name="WCF_SSLTesting.MyService">

                <host>
                    <baseAddresses>
                        <add baseAddress = "
    http://localhost/services/MyService/" />
                    </baseAddresses>
                </host>

            <endpoint address="" binding="basicHttpBinding" bindingConfiguration="mybasicHttpBinding" contract="WCF_SSLTesting.IService" />               
            <endpoint address="mex" binding="mexHttpsBinding" contract="IMetadataExchange"/>       
          </service>     
    </services>

    Конфигурация нашего сервиса завершена, теперь выполним Build проекта и опубликуем наш сервис. Для публикации необходимо выбрать пункт меню Build - Publish и далее указать путь для публикации, например “http://localhost/services

    Шаг 3. Создание клиента

    1) Создадим новый проект.

    2) Добавим Service Reference на наш сервис (в данном случае путь – “https://localhost/services/WCF_SSLTesting.MyService.svc”)

    3) Напишем код вызывающий метод нашего сервиса:

    class Program
        {
            static void Main()
            {
                using (var client = new Services.ServiceClient())
                {
                    client.Open();
                    Console.WriteLine(client.Sum(5, 2));
                    Console.ReadLine();
                }
            }
        }

    На этом наша программа завершена, сервис настроен и работает.

    Исходный код решения вы можете скачать по следующей ссылке: WCF_SSLTesting.rar

    PS Этим примером я начинаю цикл статей “WCF - это просто”, где попытаюсь подробно рассказать о наиболее интересных решениях на основе технологии Windows Communication Foundation.

    Создание отказоустойчивой системы сервисов на основе WCF 4

    Работая над высоконагружаемым веб-сайтом, наша команда часто сталкивалась с ситуацией, когда один из серверов, по тем или иным причинам, отказывал. И хотя эту ситуацию нельзя было назвать приятной, наши пользователи ничего не чувствовали, поскольку "упавший" сервер автоматически заменялся другим сервером из кластера.

     

    В этой статье я расскажу, как построить похожую отказоустойчивую систему веб-сервисов, используя средства технологии Microsoft Windows Communication Foundation 4.

     

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

    Чтобы этого избежать у нас должны быть запасные сервисы, которые бы приняли нагрузку в случае отказа основного сервиса и система маршрутизации, которая бы отправляла запросы только на работающий  сервис.

     

    Наше решение будет состоять из следующих компонентов:

    1.     BaseService - основной сервис;

    2.     BackupService - запасной сервис;

    3.     RoutingService - сервис маршрутизации;

    4.     ClientApplication - клиентское приложение, которое будет тестировать работоспособность нашей системы.

     

    Шаг 1. Создадим BaseService и BackupService сервисы.

     

    Оба эти сервиса выполняют один и тот же контракт. В данном примере контракт предусматривает единственный метод, который возвращает пользователю имя сервиса, который выполнил его запрос.

     

    [ServiceContract(Namespace = "http://RoutingTest")]

        public interface IMyService

        {

            [OperationContract]

            string GetServiceName();

        }

     

    Оба сервиса используют wsHttpBinding и расположены на следующих адресах:

    BaseService"http://localhost:8000/services/baseservice"

    BackupService"http://localhost:8000/services/backupservice"

     

    Шаг 2. Создадим сервис маршрутизации RoutingService.

     

    Для этого сервиса мы не будем сами определять его контракт и реализацию. Мы задействуем стандартный сервис системы маршрутизации WCF 4. Для этого к новому проекту необходимо добавить ссылку на сборку «System.ServiceModel.Routing».

    Сервисом у нас будет выступать класс System.ServiceModel.Routing.RoutingService, а контрактом - интерфейс System.ServiceModel.Routing.IRequestReplyRouter.

     

    В App.config внесем необходимую конфигурацию для хостинга нашего сервиса маршрутизации:

    <services> 

          <service behaviorConfiguration="routingData" name="System.ServiceModel.Routing.RoutingService">

            <host>

              <baseAddresses>

                <add  baseAddress="http://localhost:8000/routingservice/router"/>

              </baseAddresses>

            </host>

            <endpoint address="" binding="wsHttpBinding" name="reqReplyEndpoint" contract="System.ServiceModel.Routing.IRequestReplyRouter" />

            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"  />

          </service>

        </services>

       

        <behaviors>

          <serviceBehaviors>

            <behavior name="routingData">

              <serviceMetadata httpGetEnabled="True"/>

            </behavior>

          </serviceBehaviors>

        </behaviors>

     

    Шаг 3. Сконфигурируем сервис маршрутизации, так чтобы он отправлял приходящие запросы на наши сервисы.

     

    В раздел <client> внесем конечные точки ранее созданных сервисов:

    <client>

          <endpoint name="BaseService" address="http://localhost:8000/services/baseservice" binding="wsHttpBinding" contract="*" />

          <endpoint name="BackupService" address="http://localhost:8000/services/backupservice" binding="wsHttpBinding" contract="*" />

    </client>

     

    Настройки для маршрутизации находятся в разделе <routing>:

    В разделе <filters> находится единственный фильтр. Тип "MatchAll" означает, что данный фильтр будет обрабатывать все запросы, приходящие на сервис:

    <filters>

            <filter name="MatchAllFilter" filterType="MatchAll" />

    </filters>

     

    Создадим таблицу фильтров, в которой укажем основной сервис и название списка запасных сервисов:

    <filterTables>

      <filterTable name="MyRoutingTable">

         <add filterName="MatchAllFilter" endpointName="BaseService" backupList="MyBackupList" />

      </filterTable>

    </filterTables>

     

    Далее зарегистрируем список запасных сервисов:

    <backupLists>

       <backupList name="MyBackupList">

          <add endpointName="BackupService" />         

       </backupList>

    </backupLists>

     

    Теперь осталось только связать наш сервис с таблицей фильтров. Для этого раздел <behaviors> изменим следующим образом:

    <behaviors>

       <serviceBehaviors>

          <behavior name="routingData">

               <serviceMetadata httpGetEnabled="True"/>

               <routing filterTableName="MyRoutingTable" />

          </behavior>

       </serviceBehaviors>

    </behaviors>

     

    На этом настройка сервиса маршрутизации завершена.

     

    Шаг 4. Настройка клиента для тестирования сервисов

     

    Создадим новое консольное приложение и добавим в него Service Refference на сервис BaseService(предварительно его запустив). Выполняя эту операцию, студия создаст прокси-класс для нашего сервиса и внесет необходимую конфигурацию в файл App.config. Нам будет необходимо немного изменить эту конфигурацию, в частности изменить адрес конечной точки:

    вместо адреса конкретного сервиса:

                address="http://localhost:8000/services/baseservice"

    вставим адрес нашего сервиса маршрутизации:

    address="http://localhost:8000/routingservice/router"

     

    Таким образом, мы настроили клиента на взаимодействие с сервисом маршрутизации. Теперь. создав экземпляр класса MyServiceClient, мы можем на клиенте вызвать метод нашего сервиса GetServiceName().

    После прихода запроса на RoutingService система маршрутизации WCF проверит, доступен ли основной сервис и, если это не так, то отправит запрос на запасной сервис.

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

     

    Выводы:

     

    Приведенный пример демонстрирует лишь малую часть возможностей системы маршрутизации WCF 4, но, несомненно, свидетельствует о значительных возможностях технологии WCF в сфере построения отказоустойчивых и высоконагружаемых приложений.

     

    Скачать исходный код решения вы можете по следующей ссылке: WCFRoutingTest.zip