Программирование HTTP с использованием WCF

ОГЛАВЛЕНИЕ

В момент первоначального выпуска служб Windows Communication Foundation (WCF) в составе Microsoft® .NET Framework 3.0 в эти службы входила универсальная объектная модель для обмена сообщениями в формате POX (Plain Old XML) и SOAP с использованием широкого набора средств передачи данных. Поскольку в WCF включена также основательная поддержка стандартов веб-служб WS-*, она может взаимодействовать с другими современными платформами служб с относительно небольшими сложностями. WCF в среде .NET Framework 3.0 является в высшей степени расширяемой и включает в себя надежные функции управляемости, такие как ведение журналов сообщений, отслеживание операций, управление в реальном времени и элементы управления цепочками обсуждений.

WCF в среде .NET Framework 3.5 построена на точках расширения в .NET Framework 3.0 с целью включения первоклассной поддержки для создания служб, в которых соблюдаются принципы веб-технологии. Эта поддержка обеспечивается удобной в использовании моделью программирования HTTP, возможностями обмена сообщениями в формате JSON (JavaScript Object Notation) и новым интерфейсом API объединения, облегчающим создание и использование сводного содержимого. С таким набором компонентов WCF является теперь платформой служб, рекомендуемой для использования с целью подключения служб к веб-клиентам, будь это элементы управления AJAX для ASP.NET , клиенты SilverlightTM или даже обозреватели. Эти компоненты работают также в ситуациях с частично доверительными отношениями (например, при среднем уровне доверия в ASP.NET), поэтому службы WCF можно размещать в широкодоступных средах размещения. Для полноты набора возможностей в Visual Studio® 2008 встроен также инструментарий, значительно сокращающий время, затрачиваемое на активирование и запуск службы на выполнение.

Объединение передачи данных, ориентированной на веб-технологии, со стандартами SOAP и WS-* в одну модель стека служб и объектов является одной из возможностей, делающих службы WCF в среде .NET Framework 3.5 такими привлекательными. Это означает, что можно создать службу, осуществляющую передачу данных в рамках предприятия или за его пределы с использованием SOAP и WS-*, и можно настроить эту же самую службу на внешнюю передачу данных с использованием веб-протоколов. По существу, WCF берет на себя заботу о магистралях передачи сообщений в вашей службе, позволяя сосредоточить основные усилия на разработке функциональных возможностей службы.

В данной статье обсуждаются некоторые из новых, ориентированных на веб-технологии, возможностей WCF в среде .NET Framework 3.5. Статья начинается с определяющего уровень обсуждения некоторых важных архитектурных принципов протокола HTTP и Интернета, после чего обсуждается модель программирования HTTP в среде WCF и, в конце, новый интерфейс API объединения.


Основы сообщений HTTP

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

HTTP является транспортным средством глобальной сети. Протоколу HTTP требуется шаблон обмена сообщениями (MEP) для запроса и отклика. Каждый раз при использовании обозревателя для перехода на веб-страницу происходит следующее: запрашивается ресурс и возвращается один отклик, содержащий этот ресурс. Транспорт HTTP определяет несколько глаголов (называемых также методами), надстраивающих функции на шаблоне запроса или отклика MEP, например GET, POST, PUT и DELETE. Чтобы проиллюстрировать некоторые различия между глаголами HTTP, рассмотрим подробнее глагол POST протокола HTTP. Приведем укороченную версию:

POST /myservice/PostAlbum HTTP 1.1
HOST: www.cloudsample.net
<albumInfo>
  <albumId>15</albumId>
</albumInfo>

По существу, это просто сообщение, отправляемое ожидающему вызова приложению. В данном случае сообщение отправляется по адресу http://www.cloudsamples.net/myservice/PostAlbum. В этом сообщении содержатся также полезные данные, в которые входит значение для поля albumId. Выделяя основное, можно сказать, что глагол POST протокола HTTP представляет собой способ передавать полезные данные приложению, ожидающему вызова.

В данном примере полезные данные настолько просты, что они целиком могли бы быть представлены числом 15. Чтобы эта схема работала, принимающее приложение должно знать, что число 15 соответствует значению поля albumId. Эта возможность наряду с другими является одной из основных, предлагаемых глаголом GET протокола HTTP. Например, преобразуя предыдущий HTTP POST в HTTP GET, мы получаем следующее:

GET /myservice/GetAlbum/15 HTTP 1.1
HOST: www.cloudsamples.net

HTTP GET является оптимизированным HTTP POST и позволяет избавиться от необходимости использования полезной нагрузки. По существу, полезной нагрузкой является URI-адрес. Результатом этого является уменьшение размеров сообщений, что, в свою очередь, приводит к сокращению времени передачи. Уменьшение размеров сообщения и надежность URI-адреса обеспечивают еще одно положительное следствие: другим приложениям (прокси или обозревателям) проще кэшировать результаты предыдущих сообщений HTTP GET для одного и того же ресурса. Важно отметить, что HTTP GET используется чаще, чем любой другой глагол HTTP.

По своей сути, HTTP GET является запросом некоторого ресурса. Чтобы проиллюстрировать это, представим, что вы ищете интересующий вас ресурс в Интернете (скажем, http://www.cloudsamples.net/pictureservices) и собираетесь сообщить о нем другу по электронной почте. Когда ваш друг получает сообщение электронной почты, предполагается, что он, щелкнув ссылку, увидит ту же страницу, которую просматривали вы. Понятно, что вы будете удивлены, если ресурс по адресу http://www.cloudsamples.net/pictureservices внезапно окажется страницей с последними новостями от MSNBC. На самом деле URI-адрес, используемый в HTTP GET, действительно является представлением ресурса, и вы, как правило, не предполагаете, что ресурс изменится.

В то время как имеется полное согласие относительно того, что представляет HTTP GET, это не так для других глаголов HTTP. Представим реализацию отправки сообщения HTTP DELETE принимающему приложению. Должно ли принимающее приложение изменить свое состояние или выполнить то же действие? Обычно глагол HTTP DELETE связан с субтрактивным изменением состояния или действием, но особенности этого изменения состояния не определяются. То же самое верно и для HTTP PUT. Глагол HTTP PUT, как правило, связан с некоторым типом аддитивного изменения состояния или действием, но точная форма этого изменения меняется от приложения к приложению.


Представление дополнительных сведений

Приложение, получающее HTTP GET, использует сведения, встроенные в URI-адрес, для определения ресурса, который должен быть отправлен в качестве ответа. Чтобы проиллюстрировать это, рассмотрим следующий набор URI-адресов:

contoso.com/artists/Flaming+Hammer/HitMe
contoso.com/artists/Northwind/Overdone

Для этого случая у корпорации Contoso имеется приложение, обслуживающее ресурсы, имеющие отношение к музыке. Можно попытаться вывести из этих примеров способ, с помощью которого имя исполнителя и название альбома сопоставляется URI-адресу:

contoso.com/artists/[artist]/[album]

Когда приложение получает сообщение HTTP GET для http://contoso.com/artists/Northwind/Overdone, оно должно вернуть ресурс, связанный с альбомом Northwind's Overdone. Понятно, что имеется шаблон URI-адреса. Он состоит из некоторой базовой информации (contoso.com/artists) и некоторых сегментов URI-адреса (или «дыр»), которые будут содержать значения, соответствующие исполнителю и альбому.

Так же распространенным приемом является внедрение информации некоторого типа в параметры строки запроса. Хотя этот формат отличается от предыдущих примеров, конечные результаты будут одинаковыми. Приведем набор «дыр» URI-адреса с параметром строки запроса:

contoso.com/artists/Flaming+Hammer?album=HitMe
contoso.com/artists/Northwind?album=Overdone

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

contoso.com/artists/[artist]?album=[album]

Транспортный протокол HTTP для представления дополнительной информации использует также расширяемый набор заголовков. Эта информация может иметь отношение к кэшированию, типу данных в сообщении, имени приложения-отправителя, длине содержимого, операционной системе размещения и многому другому. На рис. 1 показано сообщение HTTP GET (строки 1-8) и ответное сообщение (строки 9-14) с несколькими распространенными заголовками.

Рис. 1 HTTP GET Request and Response

1    GET /PictureServices/Feed.svc/picture/Pictures;Bridge.jpg HTTP/1.1
2 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, 
  application/x-ms-application, application/vnd.ms-xpsdocument, 
  application/xaml+xml, application/x-ms-xbap, 
  application/vnd.ms-excel, application/vnd.ms-powerpoint, 
  application/msword, application/x-shockwave-flash, 
  application/x-silverlight, */*
3 Accept-Language: en-us
4 UA-CPU: x86
5 Accept-Encoding: gzip, deflate
6 User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; SLCC1; 
  .NET CLR 2.0.50727; InfoPath.2; .NET CLR 1.1.4322; 
  .NET CLR 3.5.20706; .NET CLR 3.0.590; MS-RTC LM 8)
7 Host: www.cloudsamples.net
8 Proxy-Connection: Keep-Alive

9 HTTP/1.1 200 OK
10 Content-Type: image/jpeg
11 Server: Microsoft-IIS/7.0
12 X-Powered-By: ASP.NET
13 Date: Sat, 15 Sep 2007 18:57:11 GMT
14 Content-Length: 106333

Заголовок Accept в запросе HTTP GET указывает на предпочтительный для клиента формат данных. Как видно из длинного списка значений, клиент может принимать несколько типов данных (изображения, документы office, приложения Silverlight и т.д.). В отклике на HTTP GET содержатся данные, формат которых описывается значением заголовка Content-Type (image/jpeg, как указано в строке 10). Этот простой механизм позволяет отправляющему и принимающему приложению согласовывать форматы данных. Использование заголовков для согласования форматов данных является не таким выразительным средством, как грамматики языков WSDL (Web Services Description Language) и формата XSD (XML Schema Definition) веб-служб SOAP, но его вполне достаточно для работы в среде Интернета.


REST и Интернет

При рассмотрении некоторых основных компонентов транспортного протокола HTTP выявляются некоторые важные принципы, удерживающие Интернет в виде слабо связанного единого целого. В 2000 году аспирант по имени Рой Филдинг (Roy Fielding) завершил работу над диссертацией, в которой, среди прочего, эти принципы были закодированы в виде архитектурного стиля, известного под названием REST (Representational State Transfer). (Диссертацию Роя можно найти по адресу ics.uci.edu/~fielding/pubs/dissertation/top.htm.)

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

  • Использование URI-адресов
  • Специальные свойства HTTP GET
  • Модель данных, определяемая типом содержимого

В целях данной статьи эти вопросы будут рассматриваться с точки зрения принципов Интернета. Встраивание этих принципов в приложения приводит к значительному расширению возможностей взаимодействия и получения доступа к функциям приложений. Взаимодействие и получение доступа являются основной движущей силой большинства инициатив веб-служб. В конце концов, веб-служба фактически является способом предоставления некоторых функций посредством структурированной схемы обмена сообщениями. На самом деле, принципы Интернета (запрос и отклик HTTP) описывают подмножество возможных схем обмена сообщениями. Практически все платформы, приложения, операционные системы и пользователи понимают принципы Интернета, поэтому внедрение их в приложение веб-службы увеличивает ее достижимость и возможности взаимодействия.

Важно отметить, что принципы Интернета не уводят в тень WS-*, поскольку выбор между REST и WS-* не приводит к их взаимоисключению. Ни один, ни другой не в состоянии удовлетворить все типы требований обмена данными, поэтому я настойчиво призываю избегать исключительного применения одного из них ко всем создаваемым службам. Например, REST не очень удобен для отправки сообщений промежуточному средству, в то время как WS-* прекрасно с этим справляется. С другой стороны, приложения WS-* не очень хороши при взаимодействии с простыми клиентами типа обозревателей, но приложения REST делают это безукоризненно.


Программирование HTTP с использованием WCF

Используя в качестве основы эти идеи веб-технологии, рассмотрим применение WCF в среде .NET Framework 3.5 для создания приложений, в которых соблюдаются данные принципы. Начнем с изучения некоторых новых стандартных блоков модели программирования HTTP и продолжим, продвигаясь к верхнему уровню системы.

Если предполагается «использовать URI-адреса», возможно, придется освоить создание и анализ URI-адресов. Выполнение этой задачи вручную является утомительным и сопровождается возникновением ошибок. Однако новые типы System.UriTemplate и System.UriTemplateMatch берут на себя сложную работу по созданию и анализу URI-адресов и некоторых основополагающих стандартных блоков для новых функций WCF на основе HTTP.

Тип UriTemplate является основным стандартным блоком для определения «дыр» URI-адреса, обсуждавшихся ранее. Конструктор типа UriTemplate принимает строку String, представляющую «дыры» в URI-адресе. После создания экземпляра объект UriTemplate предоставляет два метода экземпляров для привязки текстовых значений к «дырам», определенным при создании экземпляра. Эти методы возвращают URI-адрес, дыры которого наполняются значениями.

Объект UriTemplate также предоставляет метод экземпляра с именем Match для извлечения значений из «дыр» объекта System.Uri. Метод экземпляра Match принимает два объекта System.Uri. Первый Uri является базовым адресом, а второй — кандидатом на совпадение. Объект System.UriTemplateMatch возвращается из метода Match. Объект UriTemplateMatch содержит коллекцию значений для «дыр», определенных при создании экземпляра UriTemplate.

Код на рис. 2 демонстрирует способ использования типов UriTemplate и UriTemplateMatch для обращения к Uri. Результатом работы этого кода являются следующие данные:

Рис. 2 Creating a Template-Based URI

// create a URI bound to the template
Uri baseAddress = new Uri(@"http://localhost:2000");
UriTemplate template = new UriTemplate("{artist}?album={album}");
Uri boundUri = template.BindByPosition(baseAddress, "Northwind", 
  "Overdone");
Console.WriteLine(boundUri.ToString());
   
// retrieve the value of the artist segment
UriTemplateMatch match = template.Match(baseAddress, boundUri);
String bandName = match.BoundVariables["artist"];
Console.WriteLine("the name of the artist is {0}", bandName);

http://localhost:2000/Northwind?album=Overdone
the name of the artist is Northwind

У типов System.UriTemplate и System.UriTemplateMatch отсутствует обычное для WCF специальное имя System.ServiceModel в пространстве имен. Это связано с тем, что их набор функций не обязательно использовать внутри приложения WCF. Они всегда под рукой, когда требуется создать или проанализировать URI-адрес.

Хотя типы UriTemplate и UriTemplateMatch очень удобны в использовании, группа разработки WCF стремилась к тому, чтобы повысить удобство использования этих двух типов в службе WCF. Цель заключалась в том, чтобы предложить простой и интуитивно понятный способ отображения сегментов URI-адреса и параметров строки запроса в функции приложений. Полагаю, что модель, приведенная в следующем коде, отвечает всем требованиям:

[ServiceContract]
public interface IPictureService
{
  [OperationContract]
  [WebGet(UriTemplate = "picture/{pictureId}")]
  Stream GetPicture(String pictureId);

  [OperationContract]
  [WebGet(UriTemplate = "picture/t/{pictureId}")]
  Stream GetPictureThumbnail(String pictureId);

UriTemplate предстает в виде свойства экземпляра для типа WebGetAttribute, который, в свою очередь, применяется к контракту службы. Отметим, что маркер в фигурных скобках отображается на имя параметра метода. Хотя в этом примере показан только один параметр, можно добавлять много параметров, располагать их в любом порядке, добавлять параметры строки запроса и даже использовать подстановочные символы.

На этапе выполнения значение свойства UriTemplate передается конструктору UriTemplate. Как среда выполнения клиента, так и диспетчер используют значение свойства UriTemplate этим же способом. Диспетчер использует его для установления соответствия входящих сообщений и операции, а клиент использует его для обеспечения отправки по правильному URI-адресу метода с прокси. Это удобный в использовании, простой и эффективный способ «использования URI-адресов» в службах.


Глаголы HTTP в контрактах WCF

Программирование HTTP с использованием WCF в среде .NET Framework 3.5 облегчает отображение операций в контракте службы на глаголы HTTP. Возможно, глядя на имя WebGetAttribute, вы догадываетесь, что применение WebGetAttribute к операции обеспечивает доступ к этой операции посредством HTTP GET. Как видно из предыдущего фрагмента кода, WebGetAttribute определяет свойство экземпляра с именем UriTemplate.

Существуют другие свойства экземпляра для типа WebGetAttribute. Наиболее существенными являются RequestFormat и ResponseFormat. Как видно из их имен, значения этих свойств задают формат сообщения для операции. Свойства RequestFormat и ResponseFormat имеют тип WebMessageFormat — перечислимый тип, имеющий два значения: Xml и Json. В инфраструктуре WCF эти значения используются для настройки стека канала с помощью соответствующего кодировщика сообщений. Значение свойства WebMessageFormat.Xml дает стек канала, использующий кодировщик XML. Установка значения WebMessageFormat.Json приводит к стеку канала, использующему кодировщик JSON, входящий в .NET Framework 3.5. Поскольку существуют свойства для обеих сторон обмена сообщениями HTTP, приложение может принимать сообщение XML, а возвращать сообщение JSON (или наоборот).

Еще одним атрибутом, связанным с глаголами HTTP, является атрибут WebInvokeAttribute. Применение этого атрибута к операции обеспечивает доступ к ней посредством любого глагола HTTP, отличного от глагола HTTP GET. Отделение глагола HTTP GET в модели объекта от других глаголов отражает частоту использования HTTP GET по сравнению с другими глаголами HTTP. Модель объекта WebInvokeAttribute подобна WebGetAttribute, но в нее входит свойство экземпляра, названное Method. Значение этого свойства сопоставляет операции конкретный глагол HTTP. Свойство Method имеет тип String, поэтому в качестве значения можно задавать любой стандартный глагол HTTP и даже нестандартные глаголы HTTP. На рис. 3 показано, как использовать WebInvokeAttribute в контракте службы.

Рис. 3 Using WebInvokeAttribute

[ServiceContract]
public interface IPictureService
{
  [OperationContract]
  [WebGet(UriTemplate = "picture/{pictureId}")]
  Stream GetPicture(String pictureId);

  [OperationContract]
  [WebGet(UriTemplate = "picture/t/{pictureId}")]
  Stream GetPictureThumbnail(String pictureId);
  
  [OperationContract]
  [WebInvoke(UriTemplate="update", Method="POST")]
  void UpdatePictureInfo(PictureInfo info);
}

У метода UpdatePictureInfo имеется параметр с типом PictureInfo. На этапе выполнения сериализованная версия объекта PictureInfo является полезной нагрузкой сообщения HTTP POST. Передача данных этим способом дает приложениям возможность доставлять сложные типы данных, для которых нет другого способа представления в URI.
WCF в среде .NET Framework 3.5 облегчает также взаимодействие с заголовками HTTP. Заголовки запросов и откликов HTTP представляются посредством типа System.ServiceModel.Web.WebOperationContext. Тип WebOperationContext является расширением типа System.ServiceModel.OperationContext, появившегося в WCF в среде .NET Framework 3.0, и используется по аналогичной схеме. Оба они предназначены для использования в реализациях объекта службы. Тип WebOperationContext предоставляет члены, упрощающие чтение или установку значений заголовков HTTP, а также извлечение информации об URI-адресе, используемом для доступа к объекту службы. Заголовки HTTP хранятся в виде коллекции, и наиболее распространенные предоставляются в виде отдельных свойств. В данном примере показан способ использования типа WebOperationContext с целью следования модели Интернета посредством установки заголовка Content-Type для ответа HTTP:

public Stream GetPicture(string pictureId)
{
  // retrieve the Stream (omitted)
  Stream stream;

  // set the Content-Type to image/jpeg
  WebOperationContext.Current.OutgoingResponse.ContentType = 
  "image/jpeg";

  return stream;
}


Привязка и размещение

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

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

В WCF в среде .NET Framework 3.5 включена привязка с именем System.ServiceModel.WebHttpBinding. Эта привязка является обобщением принципов Интернета, и она следует той же модели использования, что и типы привязок в среде .NET Framework 3.0, поэтому ее можно добавить к конечной точке, как и любую другую привязку.

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

В WCF в среде .NET Framework 3.5 включено поведение конечной точки с именем System.ServiceModel.Description.WebHttpBehavior. Это поведение выполняет несколько задач. Для принимающих приложений оно устанавливает фильтрующую инфраструктуру с целью обеспечения, среди прочего, доставки полученных сообщений соответствующему методу объекта службы. В .NET Framework 3.0 среда WCF использовала в качестве ключа доставки сочетание действия SOAP и адреса назначения. Механизм фильтрации, настроенный WebHttpBehavior, расширяет существующие механизмы фильтрации с целью отключения совпадений UriTemplate и глаголов HTTP.

На рис. 4 показан способ использования WebHttpBinding и WebHttpBehavior для создания принимающего приложения, охватывающего все принципы Интернета. Преимуществом этого подхода является возможность добавления одной из этих конечных точек к существующему ServiceHost и за счет этого получения одного ServiceHost, обладающего как конечными точками SOAP/WS-*, так и конечными точками REST.

Рис. 4 Using WebHttpBinding and WebHttpBehavior

ServiceHost host = new ServiceHost(typeof(IPictureContract), 
  new Uri("http://localhost:5000"));
// instantiate a WebHttpBinding
WebHttpBinding binding = new WebHttpBinding();

// add an endpoint, using the WebHttpBinding
ServiceEndpoint ep = host.AddServiceEndpoint(
  typeof(IPictureContract), binding, String.Empty);

// add the WebHttpBehavior to the endpoint
ep.Behaviors.Add(new WebHttpBehavior());

// open the ServiceHost to start listening
host.Open(); 

Отправляющие приложения следуют этой же модели использования, но WebHttpBehavior следует добавить к коллекции поведений конечных точек объекта ChannelFactory<T>. Что касается обычной методики WCF, эти возможности доступны также в файлах настройки.

WCF в среде .NET Framework 3.5 развивает дальше эту модель удобного использования, чтобы в некоторых случаях избавиться от необходимости добавления WebHttpBinding или WebHttpBehavior. В новый интерфейс API включено два типа, служащих этой цели: System.ServiceModel.Web.WebServiceHost и System.ServiceModel.Web.WebServiceHostFactory. Тип WebServiceHost автоматически добавляет WebHttpBehavior ко всем конечным точкам и на этапе выполнения проводит некоторую дополнительную проверку с целью обеспечения совместимости всех конечных точек с этим поведением. Это устраняет этап добавления WebHttpBehavior к конечной точке.

Тип WebServiceHostFactory был разработан для использования в случаях размещения в IIS, чтобы избавиться от необходимости в каком-либо файле настройки или императивном файле, содержащем информацию о размещении. В WCF в среде .NET Framework 3.0 в качестве цели активации для размещения служб WCF в IIS введен файл .svc. Эти файлы содержат директивы, которые выглядят подобно директивам страниц в ASP.NET. Добавлением WebServiceHostFactory к свойству Factory директивы файла .svc создается ServiceHost, добавляется конечная точка, использующая соответствующий контракт с WebHttpBinding, добавляется WebHttpBehavior к конечной точке и открывается ServiceHost:

<%@ ServiceHost 
  Language="C#" 
  Service="PictureService" 
  Factory="System.ServiceModel.Web.WebServiceHostFactory" %>

В файле web.config для этой службы не содержатся никакие относящиеся к WCF записи.


Новый интерфейс API объединения

Представьте себе канал RSS. Если вы поступаете так же, как я, вы сразу же подумаете о канале новостей или блоге. Объединения (RSS и ATOM являются форматами объединения) могут выразить гораздо больше. Рассуждая абстрактно, объединенное содержимое является способом представления набора данных. Этот набор данных может быть практически чем угодно: числом вомбатов в жилых районах в радиусе 50 миль вокруг Сиднея, последними 10 заказами на поставку стоимостью более 100 долларов или числом бумерангов, произведенных компанией Contoso в прошлом месяце.

В .NET Framework 3.5 включена мощная поддержка создания и использования объединенного содержимого. Инфраструктура поддерживает создание и использование как формата RSS 2.0, так и формата ATOM 1.0, различных средств добавления расширений объединения и даже возможность внедрения дополнительных форматов. Модель программирования скрывает от разработчика особенности форматов объединения, что облегчает их использование.

До выхода версии 3.5 не существовало стандартного способа для создания или использования объединенного содержимого с помощью .NET Framework. Хотя новые функции объединения были написаны группой WCF, они не зависят от WCF. На деле, все типы, связанные с объединением, являются частью пространства имен System.Syndication, хотя они располагаются в сборке System.ServiceModel.Web.dll. Возможности объединения, предоставляемые .NET Framework 3.5, можно использовать из любого процесса, в котором размещается AppDomain (ASP.NET, приложения WPF, службы NT и т.д.). Эта возможность подразумевает, что можно обслуживать или использовать объединенное содержимое посредством любого транспортного средства, не обязательно протокола HTTP. Однако, в сочетании с моделью программирования HTTP в WCF объединение можно добавить также и в существующие службы SOAP/WS-*.

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

После того, как SyndicationFeed заполнен набором объектов SyndicationItem, SyndicationFeedFormatter<T> может преобразовать этот канал в конкретный формат. Существует два типа, являющихся производными от SyndicationFeedFormatter<T>: Rss20FeedFormatter и Atom10FeedFormatter. Как подразумевают их имена, эти типы преобразуют экземпляр SyndicationFeed в форматы RSS 2.0 и ATOM 1.0 соответственно.


Создание SyndicationFeed

Существует два способа создания объекта SyndicationFeed. Можно создать один экземпляр и заполнить его члены вручную, либо заполнить весь SyndicationFeed из существующего канала. Оба способа просты в исполнении и скрывают от разработчика особенности реализации конкретного формата подключения. В следующем коде показан способ создания вручную объекта SyndicationFeed.

SyndicationFeed feed = new SyndicationFeed();
feed.Title.Text = "The Cybertopian Chronicle";

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

Зачастую возникает необходимость считать информацию из существующего канала и выполнить какие-то операции на основе содержимого этого канала. В новом интерфейсе API объединения предусмотрено создание SyndicationFeed и автоматическое заполнение его состояния из существующего канала. Все, что требуется для запуска, это URI-адрес существующего канала или XmlReader, предназначенный для его чтения. В следующем коде показан способ подключения к существующему каналу в Интернете и извлечение из него некоторой информации:

Uri feedUri = new Uri("http://blogs.msdn.com/justinjsmith/atom.xml");
SyndicationFeed feed = SyndicationFeed.Load(feedUri);
Console.WriteLine(feed.Title.Text);

// outputs "The Cybertopian Chronicle"

В тип SyndicationItem входит более 35 членов. Многие из них являются свойствами, связанными с установкой или извлечением полей, таких как идентификатор элемента, дата последнего обновления, заголовок или фактическое содержимое. Существуют многочисленные члены, облегчающие расширение содержимого, хранящегося в SyndicationItem. Имеется много расширений RSS и ATOM (Microsoft Simple List Extensions, Yahoo Media RSS и GeoRSS, помимо многих других), и как SyndicationFeed, так и SyndicationItem допускает расширение с целью включения любого существующего расширения RSS или ATOM.

В каналах может быть много элементов, и одновременная их загрузка является негодным вариантом в случае больших каналов. SyndicationFeed справляется с этой ситуацией, предоставляя объект SyndicationItem в качестве IEnumerable<SyndicationItem>. Эта реализация облегчает работу с большим числом объектов SyndicationItem, поскольку в ней используется преимущество применения итератора среды .NET Framework 2.0. Итерации на наборе объектов SyndicationItem можно также выполнять с помощью LINQ. Это может значительно сократить объем кода, требуемого для извлечения данных из канала.

Объединенный интерфейс API определяет несколько типов, преобразующих SyndicationFeed в форматы RSS 2.0 и ATOM 1.0. Фактически, можно создать один SyndicationFeed, заполнить его объектами SyndicationItem, затем предоставить этот канал одновременно как RSS и ATOM. На рис. 5 показано, как получить канал ATOM 1.0, преобразовать его в RSS 2.0 и вывести новое представление RSS на консоль.

Рис. 5 Transforming a Feed

// read an ATOM feed
Uri feedUri = new Uri("http://blogs.msdn.com/justinjsmith/atom.xml");
SyndicationFeed feed = SyndicationFeed.Load(feedUri);

// transform it to RSS
Rss20FeedFormatter formatter = new Rss20FeedFormatter(feed);
XmlWriter writer = XmlWriter.Create(Console.Out, null);

// write it to the Console
formatter.WriteTo(writer);
writer.Flush();

Сочетая объединенный интерфейс API с моделью программирования HTTP в среде WCF, можно предоставить канал от настроенного URI-адреса и вернуть RSS или ATOM, основанные на компоновке этого URI-адреса. На рис. 6 показано, как определить операцию в контракте службы, которая параметр строки запроса от принятого HTTP GET использует для возврата либо канала RSS, либо канала ATOM. Обратите внимание на использование SyndicationFeedFormatter<SyndicationFeed> в контракте операции. Как Rss20FeedFormatter, так и Atom10FeedFormatter являются производными от SyndicationFeedFormatter<TSyndicationFeed>.

Рис. 6 Exposing a Feed from a Customized URI

[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
[ServiceContract]
interface IPictureService {
  [OperationContract]
  [WebGet(UriTemplate="Pictures?format={format}")]
  SyndicationFeedFormatter<SyndicationFeed> Feed(String format);
}

class PictureService : IPictureService {

  public SyndicationFeedFormatter<SyndicationFeed> Feed(String format){
  // create the syndication feed 
  SyndicationFeed feed = new SyndicationFeed();

  // add the items to the feed (omitted)

  // check the argument & return the right format
  if(format.ToLower() == "rss"){
  return new Rss20FeedFormatter(feed);
  }
  return new Atom10FeedFormatter(feed);
  }
}


Окончательное объединение

Подведем итог. Мы рассмотрели некоторые основы транспорта HTTP, связь с принципами Интернета, способ использования WCF для внедрения этих принципов в приложение и способ использования нового интерфейса API объединения для обслуживания и использования каналов RSS и ATOM. С целью дальнейшего ознакомления с возможностями этих новых компонентов можно рассмотреть их совместную работу в примере приложения с именем PictureServices на веб-странице по адресу www.cloudsamples.net/pictureservices. Этот пример можно запустить в интерактивном режиме, просмотреть исходный код и загрузить весь пример. 

Формулируя кратко, PictureServices может принимать изображения с локального компьютера или из канала Flickr. Приложение использует простую модель поставщика, и в текущей ситуации имеется три поставщика: Windows Desktop Search, папка из вашей файловой системы и Flickr. Получив файлы от поставщика, приложение PictureServices объединяет эти изображения и обслуживает их локально. В обозревателе можно перейти по ссылке канала и получить отдельные изображения. В действительности, пример является более содержательным, поэтому изучайте его детально. 

Джастин Смит (Justin Smith) работает в Майкрософт в качестве технического пропагандиста по веб-службам. Основная область его деятельности — службы WCF и BizTalk. Кроме этого, Джастин является автором книги «Inside Windows Communication Foundation» (Технология Windows Communication Foundation изнутри) (издательство Microsoft Press, 2007) и часто делает доклады на конференциях, посвященных разработке программного обеспечения.