Общие сведения об архитектуре передачи данных
Windows Communication Foundation (WCF) можно рассматривать как инфраструктуру обмена сообщениями, где происходит получение, обработка и направление сообщений в пользовательский код для дальнейших действий или создание сообщений из данных, предоставленных пользовательским кодом, и доставка этих сообщений по назначению. В данном разделе, предназначенном для опытных программистов, описывается архитектура для обработки сообщений и данных, которые в них содержатся. Упрощенное изложение практических аспектов отправки и получения данных приводится в разделе Задание передачи данных в контрактах служб.
Примечание |
---|
В данном разделе представлены сведения о реализации WCF, которые не видны при просмотре объектной модели WCF. Прежде чем приступать к описанию задокументированной реализации компонента, необходимо принять во внимание два замечания. Во-первых, все представленные описания упрощены, на практике реализация может быть более сложной из-за оптимизаций или по другим причинам. Во-вторых, никогда не стоит полагаться на определенные сведения о реализации (даже задокументированные), так как они могут изменяться без уведомления от версии к версии или даже в отдельных наборах исправлений. |
Базовая архитектура
В основе возможности обработки сообщений WCF лежит класс Message, который подробно описывается в разделе Использование класса сообщений. Компоненты времени выполнения WCF можно разделить на две основные группы: стек каналов и инфраструктура служб, точкой подключения которых является класс Message.
Стек каналов отвечает за преобразование между допустимым экземпляром Message и определенным действием, соответствующим отправке или получению данных сообщения. На отправляющей стороне стек каналов берет допустимый экземпляр Message и после определенной обработки совершает некое действие, логически соответствующее отправке сообщения. Это действие может заключаться в отправке TCP- или HTTP-пакетов, постановке сообщения в очередь сообщений, записи сообщения в базу данных, сохранении его в общей папке или любой другой операции в зависимости от реализации. Наиболее распространенным действием является отправка сообщения по сетевому протоколу. На получающей стороне происходят противоположные процессы: стек каналов обнаруживает действие (например, прибытие TCP- или HTTP-пакетов или любое другое действие), обрабатывает его и преобразует в допустимый экземпляр Message.
Работа с WCF может осуществляться в форме непосредственного использования класса Message и стека каналов. Однако такой подход сложен и может занимать много времени. Более того, объект Message не поддерживает метаданные, поэтому при таком использовании WCF невозможно создавать строго типизированные клиенты WCF.
Поэтому WCF включает инфраструктуру службы, предоставляющую простую в использовании модель программирования, которую можно использовать для создания и получения объектов Message. Инфраструктура службы сопоставляет службы с типами .NET Framework через понятие контрактов служб и направляет сообщения в операции пользователей, которые представляют собой не что иное как методы .NET Framework, отмеченные атрибутом OperationContractAttribute (для получения дополнительных сведений см. раздел Создание контрактов служб). Эти методы могут содержать параметры и возвращать значения. На стороне службы инфраструктура службы преобразует входящие экземпляры Message в параметры, а возвращаемые значения — в исходящие экземпляры Message. На стороне клиента инфраструктура службы выполняет противоположные действия. Например, см. операцию FindAirfare
ниже.
[ServiceContract]
public interface IAirfareFinderService
{
[OperationContract]
int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
Предположим, операция FindAirfare
вызывается на стороне клиента. Инфраструктура службы на стороне клиента преобразует параметры FromCity и ToCity в исходящий экземпляр Message и передает его стеку каналов для отправки.
Когда экземпляр Message прибывает из стека каналов, на стороне службы инфраструктура службы извлекает из сообщения необходимые для заполнения параметров FromCity и ToCity данные, а затем вызывает метод FindAirfare
на стороне службы. Когда метод возвращается, инфраструктура службы создает из возвращенного целочисленного значения и выходного параметра IsDirectFlight экземпляр объекта Message, содержащий данную информацию. Затем инфраструктура службы передает экземпляр Message стеку каналов для отправки обратно клиенту.
На стороне клиента из стека каналов появляется экземпляр Message, содержащий ответное сообщение. Инфраструктура службы извлекает возвращаемое значение и значение IsDirectFlight и возвращает их вызывающему объекту на стороне клиента.
Класс сообщений
Подразумевается, что класс Message является абстрактным представлением сообщения, однако его структура строго связана с сообщением SOAP. Сообщение Message содержит три основных элемента информации: текст сообщения, заголовки сообщения и свойства сообщения.
Текст сообщения
В теле сообщения представляется фактическая полезная нагрузка данных сообщения. Текст сообщения всегда представляется как набор сведений XML. Это не означает, что все сообщения, создаваемые или получаемые в WCF, должны иметь формат XML. Стек каналов определяет, как интерпретировать текст сообщения. Он может отобразить текст сообщения в формате XML, преобразовать в другой формат или даже опустить его. Конечно, с большинством поставляемых WCF привязок текст сообщения представляется в виде содержимого в формате XML в основном разделе конверта SOAP.
Следует помнить, что в классе Message не обязательно содержится буфер с представляющими текст данными в формате XML. Логически сообщение Message содержит набор сведений XML, однако этот набор данных может создаваться динамически и никогда физически не существовать в памяти.
Размещение данных в теле сообщения
Не существует универсального механизма размещения данных в теле сообщения. Класс Message имеет абстрактный метод (OnWriteBodyContents), который принимает XmlDictionaryWriter. Каждый подкласс класса Message отвечает за переопределение этого метода и запись собственного содержимого. Текст сообщения логически содержит набор сведений XML, создаваемый OnWriteBodyContent. Например, рассмотрим следующий подкласс Message.
public class AirfareRequestMessage : Message
{
public string fromCity = "Tokyo";
public string toCity = "London";
//code omitted
protected override void OnWriteBodyContents(XmlDictionaryWriter w)
{
w.WriteStartElement("airfareRequest");
w.WriteElementString("from", fromCity);
w.WriteElementString("to", toCity);
w.WriteEndElement();
}
public override MessageVersion Version
{
get { throw new NotImplementedException("The method is not implemented.") ; }
}
public override MessageProperties Properties
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageHeaders Headers
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override bool IsEmpty
{
get
{
return base.IsEmpty;
}
}
public override bool IsFault
{
get
{
return base.IsFault;
}
}
}
Физически экземпляр AirfareRequestMessage
содержит только две строки ("fromCity" и "toCity"). Однако логически сообщение содержит следующий набор сведений XML:
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
Конечно, обычно сообщения не создаются таким образом, потому что сообщения, подобные предыдущему, создаются с помощью инфраструктуры службы из параметров контракта операций подобно предыдущему. Более того, класс Message содержит статические методы CreateMessage, которые могут использоваться для создания сообщений с содержимым стандартных типов: пустое сообщение, сообщение, которое содержит сериализованный в XML с DataContractSerializer объект, сообщение, которое содержит ошибку SOAP, сообщение, которое содержит XML, представленный XmlReader, и т. д.
Получение данных из тела сообщения
Данные, которые хранятся в теле сообщения, можно извлекать двумя основными способами:
Вы можете получить весь текст сообщения одновременно, вызвав метод WriteBodyContents и передавая в средство записи XML. Весь текст сообщения записывается в это средство записи. Получение всего тела сообщения одновременно также называется запись сообщения. Запись осуществляется в основном стеком каналов при отправке сообщений — как правило, какая-либо часть стека каналов получает доступ к всему телу сообщения, кодирует и отправляет его.
Еще одним способом извлечения информации из тела сообщения является вызов GetReaderAtBodyContents и получение средства чтения XML. В этом случае доступ к телу сообщения может осуществляться последовательно по мере необходимости посредством вызова методов для средства чтения. Получение тела сообщения по частям также называется прочтение сообщения. Прочтение сообщения в основном используется инфраструктурой службы при получении сообщений. Например, при использовании DataContractSerializer инфраструктура службы размещает средство чтения XML над текстом сообщения, а затем передает его механизму десериализации, который начинает считывать сообщение по элементам и создавать соответствующий граф объекта.
Текст сообщения можно извлечь только один раз. Это позволяет работать с потоками только в прямом направлении. Например, можно записать переопределение OnWriteBodyContents, выполняющее чтение из потока FileStream и возвращающее результаты в виде набора сведений XML. При этом полностью отпадает необходимость в "перемотке назад" к началу файла.
Методы WriteBodyContents и GetReaderAtBodyContents просто проверяют, что текст сообщения никогда ранее не извлекалось, а затем вызывают OnWriteBodyContents или OnGetReaderAtBodyContents соответственно.
Использование сообщений в WCF
Большинство сообщений можно разбить на две группы: исходящие (создаваемые инфраструктурой службы для отправки стеком каналов) или входящие (прибывающие из стека каналов и интерпретируемые инфраструктурой службы). Более того, стек каналов может работать в режиме буферизации или в режиме потока. Инфраструктура службы также может отображать потоковую или непотоковую модель программирования. В связи с этим необходимо привести следующую таблицу, в которой перечисляются различные варианты использования сообщений, а также сообщаются упрощенные сведения об их реализации.
Тип сообщения | Данные основного текста в сообщении | Реализация записи (OnWriteBodyContents) | Реализация чтения (OnGetReaderAtBodyContents) |
---|---|---|---|
Исходящие, созданные из непотоковой модели программирования |
Данные, необходимые для записи сообщения (например, объект и экземпляр DataContractSerializer, необходимый для его сериализации)* |
Пользовательская логика для записи сообщения на основании сохраненных данных (например, вызов метода WriteObject сериализатора DataContractSerializer, если используется именно этот сериализатор)* |
Вызов OnWriteBodyContents, буферизация результатов, возврат средства чтения XML над буфером |
Исходящие, созданные из потоковой модели программирования |
Объект Stream с записываемыми данными* |
Запись данных из сохраненного потока с помощью механизма IStreamProvider* |
Вызов OnWriteBodyContents, буферизация результатов, возврат средства чтения XML над буфером |
Входящие из потокового стека каналов |
Объект Stream, который представляет поступающие через сеть данные с помощью XmlReader над ним |
Запись содержимого из сохраненного XmlReader с помощью WriteNode |
Возвращает сохраненное средство чтения XmlReader. |
Входящие из непотокового стека каналов |
Буфер, который содержит данные основного текста и XmlReader над ними |
Записывает содержимое из сохраненного XmlReader с помощью WriteNode |
Возвращает сохраненный атрибут lang |
* Эти элементы реализуются непосредственно не в подклассах Message, а в подклассах класса BodyWriter. Дополнительные сведения о среде BodyWriter см. в разделе Использование класса сообщений.
Заголовки сообщения
Сообщение может содержать заголовки. Логически заголовок состоит из набора сведений XML, связанного с именем, пространством имен и несколькими другими свойствами. Доступ к заголовкам сообщения осуществляется с помощью свойства Headers сообщения Message. Каждый заголовок представляется классом MessageHeader. Как правило, заголовки сообщения сопоставляются с заголовками сообщения SOAP при использовании стека каналов, настроенного на работу с сообщениями SOAP.
Размещение данных в заголовке сообщения и извлечение данных из заголовка аналогичны использованию тела сообщения. Процесс несколько упрощается из-за того, что в заголовках не поддерживается потоковая передача. Доступ к содержимому одного и того же заголовка можно осуществлять более одного раза, кроме того, это можно делать в произвольном порядке, каждый раз принудительно вызывая буферизацию заголовков. Не существует универсального механизма, позволяющего получить средство чтения XML над заголовком, однако в WCF имеется внутренний подкласс MessageHeader, который представляет собой доступный для чтения заголовок с такой возможностью. Данный тип заголовка сообщения MessageHeader создается стеком каналов при поступлении сообщения с заголовками пользовательских приложений. Это позволяет инфраструктуре службы использовать механизм десериализации (например, DataContractSerializer) для интерпретации этих заголовков.
Дополнительные сведения см. в разделе Использование класса сообщений.
Свойства сообщения
Сообщение может содержать свойства. Свойство — это любой объект .NET Framework, связанный с именем строки. Доступ к свойствам осуществляется через свойство Properties сообщения Message.
В отличие от тела и заголовков сообщения (которые обычно сопоставляются с текстом и заголовками сообщения SOAP соответственно) свойства сообщения, как правило, не отправляются и не принимаются вместе с этими сообщениями. Свойства сообщения существуют в основном в качестве механизма связи для передачи данных о сообщении между различными каналами в стеке каналов, а также между стеком каналов и моделью служб.
Например, канал транспорта HTTP, включаемый в состав WCF, способен при отправке ответов клиентам создавать различные коды состояния HTTP, такие как состояние "404 (не найдено)" и "500 (внутренняя ошибка сервера)". До отправки ответного сообщения канал проверяет наличие свойства “httpResponse”, содержащего объект типа HttpResponseMessageProperty, в свойстве Properties сообщения Message. Если это свойство найдено, канал транспорта обращается к свойству StatusCode и использует указанный код состояния. Если свойство не найдено, используется код по умолчанию — «200 (ОК)».
Дополнительные сведения см. в разделе Использование класса сообщений.
Сообщение как единое целое
До сих пор мы обсуждали методы доступа к различным частям сообщения по отдельности. Однако класс Message также предоставляет методы для работы с сообщением как единым целым. Например, метод WriteMessage записывает все сообщение в средство записи XML.
Для этого необходимо задать сопоставление между всем экземпляром Message и набором сведений XML. Фактически, такое сопоставление уже существует: WCF использует стандарт SOAP для определения такого сопоставления. Когда экземпляр Message записывается как набор сведений XML, итоговый набор данных является допустимым конвертом SOAP, который содержит соответствующее сообщение. Следовательно, WriteMessage, как правило, выполняет следующие шаги:
записывает открывающий тег элемента конверта SOAP,
записывает открывающий тег элемента заголовка SOAP, записывает все заголовки и закрывает элемент заголовка,
записывает открывающий тег элемента тела сообщения SOAP,
вызывает WriteBodyContents или другой эквивалентный метод для записи тела сообщения,
закрывает элементы тела и конверта.
Описанные выше шаги тесно связаны со стандартом SOAP. Ситуация осложняется наличием нескольких версий SOAP, так как, например, невозможно правильно записать элемент конверта SOAP, не имея точной информации об используемой версии SOAP. В некоторых случаях, кроме того, желательно полностью отключить это сложное сопоставление с протоколом SOAP.
Для этих целей в сообщении Message предусмотрено свойство Version. Это свойство можно настроить на указание версии SOAP, которая должна использоваться при записи сообщения, или установить на None, чтобы запретить любые сопоставления с протоколом SOAP. Если свойство Version установлено на None, методы, работающие с целым сообщением, функционируют как если бы сообщение состояло только из тела, например метод WriteMessage в этом случае просто вызывает WriteBodyContents, а не выполняет описанную выше последовательность действий. Предполагается, что для входящих сообщений Version будет определяться автоматически и, следовательно, задаваться верно.
Стек каналов
Каналы
Как уже говорилось выше, стек каналов отвечает за преобразование исходящих экземпляров Message в какое-либо действие (например, отправку пакетов по сети) или преобразование какого-либо действия (например, получения сетевых пакетов) во входящие экземпляры Message.
Стек каналов представляет собой один или более каналов, упорядоченных определенным образом. Исходящий экземпляр Message передается первому каналу стека (также называемому верхним каналом), который передает экземпляр следующему каналу стека (каналу более низкого уровня) и т. д. Завершение сообщения происходит в последнем канале, именуемом канал транспорта. Входящие сообщения создаются в канале транспорта и передаются в стеке от каналов более низкого уровня к каналам более высокого уровня. С верхнего канала сообщение, как правило, передается в инфраструктуру службы. Хотя это стандартная процедура обработки сообщений приложения, некоторые каналы могут работать по несколько иной схеме, например они могут отправлять собственные инфраструктурные сообщения, а не передавать сообщения, полученные от каналов более высокого уровня.
Каналы могут по-разному оперировать сообщениями при прохождении последних через стек. Наиболее распространенной операцией является добавление заголовка к исходящему сообщению и чтение заголовков входящего сообщения. Например, канал может вычислить цифровую подпись в сообщении и добавить ее в качестве заголовка. Канал также может проверить заголовок входящих сообщений, содержащий цифровую подпись, и заблокировать сообщения, которые не имеют действительной подписи (т. е. запретить их передачу по стеку каналов). Каналы часто используются для установки и проверки свойств сообщения. Текст сообщения, как правило, не изменяется, хотя это и допустимо (например, безопасный канал WCF может шифровать текст сообщения).
Каналы транспорта и кодировщики сообщений
Самый нижний канал стека отвечает за фактическое преобразование исходящего сообщения Message, измененного другими каналами, в какое-либо действие. На принимающей стороне этот канал преобразует действие в сообщение Message, которое обрабатывается другими каналами.
Как уже говорилось ранее, действия могут быть самыми разнообразными: например, отправка или получение сетевых пакетов по различным протоколам, чтение или запись сообщений в базу данных, постановка сообщений в очередь сообщений или удаление сообщений из очереди. Все эти действия схожи в том, что их выполнение требует преобразования между экземпляром Message WCF и фактической группой байтов, которая может отправляться, приниматься, читаться, записываться, ставиться в очередь и удаляться из очереди. Процесс преобразования Message в группу байтов называется кодированием, а обратный процесс создания Message из группы байтов — декодированием.
Большинство каналов транспорта для кодирования и декодирования используют компоненты под названием кодировщики сообщений. Кодировщик сообщений — это подкласс класса MessageEncoder. Кодировщик MessageEncoder включает различные перегрузки методов ReadMessage и WriteMessage, позволяющие производить преобразование между сообщением Message и группами байтов.
На отправляющей стороне буферизующий канал транспорта передает объект Message, полученный от канала более высокого уровня, методу WriteMessage. Он возвращает массив байтов, который затем использует для выполнения действия (например, упаковки этих байтов в виде действительных TCP-пакетов и отправки их по назначению). Потоковый канал транспорта сначала создает поток Stream (например, через исходящее TCP-соединение), а затем передает поток Stream и сообщение Message, которые он должен отправить записывающей сообщение перегрузке соответствующего метода WriteMessage.
На получающей стороне буферизующий канал транспорта извлекает входящие байты (например, от входящих TCP-пакетов) в массив и вызывает метод ReadMessage, чтобы получить объект Message для передачи по стеку каналов. Потоковый канал транспорта создает объект Stream (например, сетевой поток по входящему TCP-подключению) и передает его ReadMessage, чтобы возвратить объект Message.
Разделение между каналами транспорта и кодировщиком сообщений не является обязательным; можно создать канал транспорта, который не использует кодировщик сообщений. Однако преимуществом подобного разделения является простота построения. Если канал транспорта использует только базу MessageEncoder, он может работать с кодировщиком сообщений WCF или любым сторонним кодировщиком. Аналогично, один и тот же кодировщик, как правило, можно использовать в любом канале транспорта.
Операции кодировщика сообщений
Для того чтобы описать стандартные операции, выполняемые кодировщиком сообщений, рекомендуется рассмотреть следующие четыре случая.
Операция | Комментарий |
---|---|
Кодирование (с буферизацией) |
В режиме буферизации кодировщик, как правило, создает буфер с переменным размером, а затем создает над ним средство записи XML. После этого кодировщик вызывает метод WriteMessage для кодируемого сообщения, который записывает заголовки и текст сообщения с помощью WriteBodyContents (см. пояснение в предыдущем подразделе данного раздела, Message). Содержимое буфера (представленное в виде массива байтов) затем возвращается каналу транспорта для дальнейшего использования. |
Кодирование (потоковое) |
При работе в потоковом режиме операция также выполняется согласно данному выше описанию, при этом ситуация упрощается тем, что нет необходимости использовать буфер. Как правило, средство записи XML создается над потоком, а метод WriteMessage вызывается для сообщения Message, чтобы записать его в это средство записи. |
Декодирование (с буферизацией) |
При декодировании в режиме буферизации, как правило, создается специальный подкласс Message, который содержит буферизованные данные. Считываются заголовки сообщения, создается средство чтения XML и устанавливается в теле письма. Это средство чтения, которое будет возвращено с GetReaderAtBodyContents. |
Декодирование (потоковое) |
При декодировании в потоковом режиме, как правило, создается специальный подкласс "Сообщения". Поток перемещается вперед ровно настолько, чтобы прочитать все заголовки и разместить их в теле сообщения. Затем над потоком создается средство чтения XML. Это средство чтения, которое будет возвращено с GetReaderAtBodyContents. |
Кодировщики могут выполнять и другие функции. Например, они могут объединять в пул средства чтения и записи XML. Не рационально создавать новое средство чтения или записи XML всякий раз, когда в них возникает необходимость. Поэтому кодировщики, как правило, поддерживают пул средств чтения и пул средств записи настраиваемого размера. В приведенных выше описаниях операций, выполняемых кодировщиком, фразы типа «создать средство чтения/записи XML», как правило, означают «взять средство чтения/записи XML из пула или создать новое средство, если в пуле отсутствует необходимое». Кодировщик (и подклассы Message, создаваемые им при декодировании) содержит логику для возвращения средств чтения и записи в пулы, если эти средства более не нужны (например, при закрытии Message).
WCF предоставляет три типа кодировщика сообщений, хотя при необходимости возможно создать дополнительные пользовательские типы. Это текстовое и двоичное кодирование и механизм оптимизации передачи сообщений (MTOM). Эти типы подробно описаны в разделе Выбор кодировщика сообщений.
Интерфейс IStreamProvider
При записи исходящего сообщения с потоковым текстом в средство записи XML Message использует в реализации OnWriteBodyContents последовательность вызовов, подобную описанной ниже:
запись всех необходимых данных, предшествующих потоку (например, открывающий тег XML);
запись потока;
запись данных, следующих за потоком (например, закрывающий тег XML).
Этот подход хорошо работает с кодировками, подобными текстовой кодировке XML. Однако некоторые кодировки не размещают информацию набора сведений XML (например, теги начальных и конечных XML-элементов) вместе с данными, которые содержатся внутри элементов. Например, при кодировании MTOM сообщение разделяется на несколько частей. Одна часть содержит набор сведений XML, в том числе, возможно, и ссылки на другие части фактического содержимого элемента. Набор сведений XML, как правило, сравнительно мал по сравнению с потоковым содержимым, поэтому имеет смысл его буферизировать, сохранить, а затем записать содержимое потоковым способом. Это означает, что к моменту записи закрывающего тега элемента поток должен быть не сохранен.
Для этого используется интерфейс IStreamProvider. Этот интерфейс содержит метод GetStream, который возвращает поток, подлежащий записи. Ниже описан правильный способ сохранения потокового тела сообщения в OnWriteBodyContents.
Запись всех необходимых данных, предшествующих потоку (например, открывающий тег XML).
Вызов перегрузки WriteValue для XmlDictionaryWriter, принимающего IStreamProvider, с реализацией IStreamProvider, возвращающей подлежащий записи поток.
Запись данных, следующих за потоком (например, закрывающий тег XML).
Используя этот подход, средство записи XML может выбирать, когда вызывать GetStream и сохранять потоковые данные. Например, средства записи текстовых и двоичных данных XML немедленно вызывают его и записывают потоковое содержимое между открывающим и закрывающим тегами. Средство записи MTOM может вызвать GetStream позже, когда будет готово к записи соответствующей части сообщения.
Представление данных в инфраструктуре службы
Как отмечается в подразделе "Базовая архитектура" данного раздела, инфраструктура службы — это часть WCF, которая, кроме всего прочего, отвечает за преобразование между удобной для пользователей моделью программирования данных сообщения и фактическими экземплярами Message. Как правило, обмен сообщениями представляется в инфраструктуре службы как метод .NET Framework, отмеченный атрибутом OperationContractAttribute. Метод может брать несколько параметров и возвращать возвращаемое значение или выходные параметры (или и то, и другое). На стороне службы входные параметры представляют входящее сообщение, а возвращаемое значение и выходные параметры — исходящее сообщение. На стороне клиента происходит обратное представление. Модель программирования для описания сообщений с помощью параметров и возвращаемого значения более подробно описана в разделе Задание передачи данных в контрактах служб. А в этом разделе приводится краткий обзор моделей.
Модели программирования
Инфраструктура службы WCF поддерживает пять разных моделей программирования для описания сообщений.
1. Пустое сообщение
Это простейший случай. Для описания пустого входящего сообщения не нужно использовать какие-либо входные параметры.
[OperationContract]
int GetCurrentTemperature();
Для описания пустого исходящего сообщения нужно использовать возвращаемое значение типа void, выходные параметры при этом не используются.
[OperationContract]
void SetDesiredTemperature(int t);
Обратите внимание, что это описание отличается от контракта односторонних операций.
[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
В примере SetDesiredTemperature
описывается шаблон двустороннего обмена сообщениями. Сообщение возвращается от операции, но при этом оно пустое. От операции также можно возвратить ошибку. В примере "Set Lightbulb" описывается шаблон одностороннего обмена сообщениями, поэтому не требуется описание исходящего сообщения. В этом случае служба не может передать обратно клиенту какое-либо состояние.
2. Использование класса сообщений напрямую
Класс Message (или один из его подклассов) можно использовать напрямую в контракте операций. В этом случае инфраструктура службы только передает сообщение Message от операции стеку каналов и обратно без какой-либо дальнейшей обработки.
Существует два основных варианта непосредственного использования Message. Его можно использовать в продвинутых сценариях, если ни одна из других моделей программирования не является достаточно гибкой для описания данного сообщения. Например, для описания сообщения можно использовать файлы на диске, тогда свойства файла становятся заголовками сообщения, а его содержимое — текстом сообщения. В этом случае можно создать что-нибудь подобное описанному ниже.
public class FileMessage : Message
// Code not shown.
Второй распространенный вариант использования Message в контракте операций применяется в случае, когда для службы не имеет значения содержимое конкретного сообщения, и служба рассматривает сообщение в качестве "черного ящика". Например, имеется служба, которая пересылает сообщения нескольким другим получателям. В этом случае контракт можно записать следующим образом.
[OperationContract]
public FileMessage GetFile()
{
//code omitted
FileMessage fm = new FileMessage("myFile.xml");
return fm;
}
Строка действия Action="*" эффективно отключает функцию перенаправления сообщений и гарантирует попадание всех сообщений, отправленных контракту IForwardingService
, в операцию ForwardMessage
. (Как правило, диспетчер анализирует заголовок действия в сообщении, чтобы определить, для какой операции оно предназначено. Action="*" означает «все возможные значения заголовка действия».) Сочетание Action="*" и использование Message в качестве параметра получило название «универсальный контракт», так как в этом случае контракт может получать все возможные сообщения. Чтобы иметь возможность отправлять все возможные сообщения, необходимо использовать Message как возвращаемое значение и установить значение "*" для ReplyAction. В этом случае инфраструктура службы не сможет добавить свой собственный заголовок действия, и вы сможете управлять этим заголовком с помощью возвращаемого вами объекта Message.
3. Контракты сообщений
WCF предоставляет декларативную модель программирования для описания сообщений, называемую контракты сообщений. Эта модель более подробно описана в разделе Использование контрактов сообщений. По существу, все сообщение представляется одним типом .NET Framework, который использует атрибуты MessageBodyMemberAttribute и MessageHeaderAttribute для описания того, какие части класса контракта сообщений с какими частями сообщения должны сопоставляться.
Контракты сообщений обеспечивают значительную степень управления над получаемыми экземплярами Message (хотя в данном случае степень управления все равно ниже, чем при непосредственном использовании класса Message). Например, тела сообщений часто состоят из многочисленных элементов данных, каждый из которых представлен собственным XML-элементом. Эти элементы могут встречаться непосредственно в теле сообщения (режим bare) или помещаться в программу-оболочку содержащегося в теле XML-элемента. Использование модели программирования контракта сообщений позволяет выбирать способ представления элементов (в режиме "bare" или в программе-оболочке) и управлять именем программы-оболочки и пространством имен.
В следующем примере кода контракта сообщений проиллюстрированы эти возможности.
[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
[MessageHeader]
public string customerID;
[MessageBodyMember]
public string item;
[MessageBodyMember]
public int quantity;
}
Элементы, отмеченные для сериализации (с атрибутами MessageBodyMemberAttribute, MessageHeaderAttribute или любыми другими связанными атрибутами), должны быть сериализуемыми — только в этом случае они смогут участвовать в контракте сообщений. Дополнительные сведения см. в разделе подраздел "Сериализация" далее в этом разделе.
4. Параметры
Часто для описания операции с несколькими элементами данных разработчику не требуется тот высокий уровень управления, который обеспечивается контрактами сообщений. Например, при создании новых служб, как правило, не приходится принимать решение о способе представления элементов (в режиме "bare" или в программе-оболочке) и выбирать имя программы-оболочки. Зачастую для принятия таких решений необходимы глубокие знания о функционировании веб-служб и протокола SOAP.
Инфраструктура службы WCF может автоматически выбирать оптимальное представление SOAP с наиболее широкими возможностями взаимодействия для отправки и получения большого числа взаимосвязанных элементов данных, освобождая, таким образом, пользователя от необходимости принимать подобные решения самостоятельно. Это возможно благодаря тому, что данные элементы данных описаны как параметры или возвращаемые значения контракта операций. Например, рассмотрим следующий контракт операций.
[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
Инфраструктура службы автоматически принимает решение о размещении всех трех элементов данных (customerID
, item
и quantity
) в теле сообщения и помещении их в программу-оболочку с именем SubmitOrderRequest
.
Рекомендуется описывать информацию, которую необходимо отправить или получить, в виде простого списка параметров контракта операций, если, конечно, не имеется особых причин, вынуждающих перейти к использованию более сложного контракта сообщений или моделей программирования на основе Message.
5. Поток
Использование потока Stream или одного из его подклассов в контракте операций либо в качестве единственной части тела сообщения в контракте сообщений может считаться отдельной моделью программирования, которая отличается от описанных выше моделей. Только использование Stream подобным образом гарантирует возможность использования контракта в потоковом режиме; во всех остальных случаях необходимо самостоятельно создавать подкласс Message, совместимый с потоковым режимом. Дополнительные сведения см. в разделе Большие наборы данных и потоковая передача.
При использовании Stream или одного из его подклассов таким образом сериализатор не вызывается. Для исходящих сообщений создается специальный потоковый подкласс Message, а поток записывается в интерфейсе IStreamProvider описанным в данном разделе способом. Для входящих сообщений инфраструктура службы создает подкласс Stream над входящими сообщениями и предоставляет этот подкласс операции.
Ограничения модели программирования
Вышеописанные модели программирования нельзя сочетать произвольно. Например, если операция принимает определенный тип контракта сообщений, этот контракт сообщений должен стать единственным входным параметром операции. Более того, после этого операция должна вернуть либо пустое сообщение (возвращаемый тип "void"), либо другой контракт сообщений. Данные ограничения моделей программирования описаны в разделах, посвященных каждой из этих моделей: Использование контрактов сообщений, Использование класса сообщений и Большие наборы данных и потоковая передача.
Модули форматирования сообщений
Описанные выше модели программирования поддерживаются подключением компонентов под названием модули форматирования сообщений в инфраструктуру службы. Модули форматирования сообщений представляют собой типы, которые реализуют интерфейс IClientMessageFormatter или IDispatchMessageFormatter (или оба) для использования в клиентах и клиентах службы WCF соответственно.
Как правило, модули форматирования сообщений подключаются поведениями. Например, поведение DataContractSerializerOperationBehavior подключает модуль форматирования сообщений контракта данных. На стороне службы это достигается благодаря установке Formatter на соответствующий модуль форматирования в методе ApplyDispatchBehavior, а на стороне клиента — благодаря установке Formatter на соответствующий модуль форматирования в методе ApplyClientBehavior.
В таблице ниже приведены методы, которые могут реализовываться модулем форматирования сообщений.
Интерфейс | Метод | Действие |
---|---|---|
IDispatchMessageFormatter |
преобразует входящее сообщение Message в параметры операции |
|
IDispatchMessageFormatter |
создает исходящее сообщение Message из возвращаемого значения/выходных параметров операции |
|
IClientMessageFormatter |
создает исходящее сообщение Message из параметров операции |
|
IClientMessageFormatter |
преобразует входящее сообщение Message в возвращаемое значение/выходные параметры |
Сериализация
Всякий раз при использовании контрактов сообщений или параметров для описания содержимого сообщений необходимо использовать сериализацию для преобразования между типами .NET Framework и представлением набора данных XML. Сериализация используется и в других частях WCF, например Message содержит универсальный метод GetBody, который можно использовать для прочтения всего тела сообщения, десериализованного в объект.
WCF поддерживает две встроенные технологии сериализации для сериализации и десериализации параметров и частей сообщений: DataContractSerializer и XmlSerializer. Более того, можно создать настраиваемые сериализаторы. Однако в других частях WCF (таких как универсальный метод GetBody или сериализация ошибок протокола SOAP) может действовать ограничение на использование каких-либо иных подклассов, кроме XmlObjectSerializer (например, разрешается использование подклассов DataContractSerializer и NetDataContractSerializer, запрещается использование подкласса XmlSerializer); более того, приложение может быть жестко запрограммировано на использование исключительно подкласса DataContractSerializer.
XmlSerializer представляет собой модуль сериализации, используемый в веб-службах ASP.NET. DataContractSerializer — это новый модуль сериализации, совместимый с моделью программирования новых контрактов данных. DataContractSerializer выбирается по умолчанию, однако с помощью атрибута DataContractFormatAttribute можно для каждой операции выбирать модуль XmlSerializer.
DataContractSerializerOperationBehavior и XmlSerializerOperationBehavior — это поведения операции, ответственные за подключение модулей форматирования сообщений для DataContractSerializer и XmlSerializer соответственно. Фактически, поведение DataContractSerializerOperationBehavior может работать с любым сериализатором, наследуемым от XmlObjectSerializer, включая NetDataContractSerializer (подробное описание см. в разделе "Использование автономной сериализации"). Поведение вызывает одну из перегрузок виртуального метода CreateSerializer для получения сериализатора. Для подключения иного сериализатора необходимо создать новый подкласс DataContractSerializerOperationBehavior и переопределить обе перегрузки метода CreateSerializer.