Поделиться через


Общие сведения об архитектуре передачи данных

Windows Communication Foundation (WCF) можно рассматривать как инфраструктуру обмена сообщениями. где происходит получение, обработка и направление сообщений в пользовательский код для дальнейших действий или создание сообщений из данных, предоставленных пользовательским кодом, и доставка этих сообщений по назначению. В данном разделе, предназначенном для опытных программистов, описывается архитектура для обработки сообщений и данных, которые в них содержатся. Упрощенное изложение практических аспектов отправки и получения данных приводится в разделе Specifying Data Transfer in Service Contracts.

Примечание.

В этом разделе рассматриваются сведения о реализации 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);
}
<ServiceContract()> _
Public Interface IAirfareFinderService

    <OperationContract()> _
    Function FindAirfare(ByVal FromCity As String, _
    ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer

End Interface

Предположим, операция 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)), который принимает 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;
        }
    }
}
Public Class AirfareRequestMessage
    Inherits Message

    Public fromCity As String = "Tokyo"
    Public toCity As String = "London"
    ' Code omitted…
    Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
        w.WriteStartElement("airfareRequest")
        w.WriteElementString("from", fromCity)
        w.WriteElementString("to", toCity)
        w.WriteEndElement()
    End Sub

    Public Overrides ReadOnly Property Version() As MessageVersion
        Get
            Throw New NotImplementedException("The method is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Properties() As MessageProperties
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property

    Public Overrides ReadOnly Property Headers() As MessageHeaders
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property


    Public Overrides ReadOnly Property IsEmpty() As Boolean
        Get
            Return MyBase.IsEmpty
        End Get
    End Property

    Public Overrides ReadOnly Property IsFault() As Boolean
        Get
            Return MyBase.IsFault
        End Get
    End Property
End Class

Физически экземпляр AirfareRequestMessage содержит только две строки ("fromCity" и "toCity"). Однако логически сообщение содержит следующий набор сведений XML:

<airfareRequest>  
    <from>Tokyo</from>  
    <to>London</to>  
</airfareRequest>  

Конечно, обычно сообщения не создаются таким образом, потому что сообщения, подобные предыдущему, создаются с помощью инфраструктуры службы из параметров контракта операций подобно предыдущему. Более того, класс Message содержит статические методы CreateMessage , которые могут использоваться для создания сообщений с содержимым стандартных типов: пустое сообщение, сообщение, которое содержит сериализованный в XML с DataContractSerializerобъект, сообщение, которое содержит ошибку SOAP, сообщение, которое содержит XML, представленный XmlReader, и т. д.

Получение данных из тела сообщения

Данные, которые хранятся в теле сообщения, можно извлекать двумя основными способами:

  • Вы можете получить весь текст сообщения одновременно, вызвав метод WriteBodyContents(XmlDictionaryWriter) и передавая в средство записи XML. Весь текст сообщения записывается в это средство записи. Получение всего тела сообщения одновременно также называется запись сообщения. Запись осуществляется в основном стеком каналов при отправке сообщений - как правило, какая-либо часть стека каналов получает доступ к всему телу сообщения, кодирует и отправляет его.

  • Еще одним способом извлечения информации из тела сообщения является вызов GetReaderAtBodyContents() и получение средства чтения XML. В этом случае доступ к телу сообщения может осуществляться последовательно по мере необходимости посредством вызова методов для средства чтения. Получение тела сообщения по частям также называется прочтение сообщения. Прочтение сообщения в основном используется инфраструктурой службы при получении сообщений. Например, при использовании DataContractSerializer инфраструктура службы размещает средство чтения XML над текстом сообщения, а затем передает его механизму десериализации, который начинает считывать сообщение по элементам и создавать соответствующий граф объекта.

Текст сообщения можно извлечь только один раз. Это позволяет работать с потоками только в прямом направлении. Например, вы можете записать переопределение OnWriteBodyContents(XmlDictionaryWriter) , выполняющее чтение из потока 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см. в разделе Using the Message Class.

Заголовки сообщений

Сообщение может содержать заголовки. Логически заголовок состоит из набора сведений XML, связанного с именем, пространством имен и несколькими другими свойствами. Доступ к заголовкам сообщения осуществляется с помощью свойства Headers сообщения Message. Каждый заголовок представляется классом MessageHeader . Как правило, заголовки сообщения сопоставляются с заголовками сообщения SOAP при использовании стека каналов, настроенного на работу с сообщениями SOAP.

Размещение данных в заголовке сообщения и извлечение данных из заголовка аналогичны использованию тела сообщения. Процесс несколько упрощается из-за того, что в заголовках не поддерживается потоковая передача. Доступ к содержимому одного и того же заголовка можно осуществлять более одного раза, кроме того, это можно делать в произвольном порядке, каждый раз принудительно вызывая буферизацию заголовков. Нет механизма общего назначения для получения средства чтения XML через заголовок, но существует MessageHeader подкласс внутренний в WCF, представляющий удобочитаемый заголовок с такой возможностью. Этот тип заголовка MessageHeader создается стеком каналов, когда приходит сообщение с заголовками пользовательского приложения. Это позволяет инфраструктуре службы использовать для интерпретации этих заголовков механизм десериализации, такой как DataContractSerializer.

Дополнительные сведения см. в разделе "Использование класса сообщений".

Свойства сообщения

Сообщение может содержать свойства. Свойство — это любой объект платформа .NET Framework, связанный со строковым именем. Доступ к свойствам осуществляется через свойство Properties сообщения Message.

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

Например, http-транспортный канал, включенный в состав WCF, может создавать различные коды состояния HTTP, такие как "404 (не найдено)" и "500 (внутренняя ошибка сервера), при отправке ответов клиентам. Перед отправкой сообщения ответа проверка, чтобы узнать, содержит ли PropertiesMessage свойство httpResponse, содержащее объект типаHttpResponseMessageProperty. Если это свойство найдено, канал транспорта обращается к свойству StatusCode и использует указанный код состояния. Если свойство не найдено, используется код по умолчанию - «200 (ОК)».

Дополнительные сведения см. в разделе "Использование класса сообщений".

Сообщение как единое целое

До сих пор мы обсуждали методы доступа к различным частям сообщения по отдельности. Однако класс Message также предоставляет методы для работы с сообщением как единым целым. Например, метод WriteMessage записывает все сообщение в средство записи XML.

Для этого необходимо задать сопоставление между всем экземпляром Message и набором сведений XML. Такое сопоставление, на самом деле, существует: WCF использует стандарт SOAP для определения этого сопоставления. Когда экземпляр Message записывается как набор сведений XML, итоговый набор данных является допустимым конвертом SOAP, который содержит соответствующее сообщение. Следовательно, WriteMessage , как правило, выполняет следующие шаги:

  1. записывает открывающий тег элемента конверта SOAP,

  2. записывает открывающий тег элемента заголовка SOAP, записывает все заголовки и закрывает элемент заголовка,

  3. записывает открывающий тег элемента тела сообщения SOAP,

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

  5. закрывает элементы тела и конверта.

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

Для этих целей в сообщении Version предусмотрено свойство Message. Это свойство можно настроить на указание версии SOAP, которая должна использоваться при записи сообщения, или установить на None , чтобы запретить любые сопоставления с протоколом SOAP. Если свойство Version установлено на None, методы, работающие с целым сообщением, функционируют как если бы сообщение состояло только из тела, например метод WriteMessage в этом случае просто вызывает WriteBodyContents , а не выполняет описанную выше последовательность действий. Предполагается, что для входящих сообщений Version будет определяться автоматически и, следовательно, задаваться верно.

Стек каналов

Каналы

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

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

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

Каналы транспорта и кодировщики сообщений

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

Как уже говорилось ранее, действия могут быть самыми разнообразными: например, отправка или получение сетевых пакетов по различным протоколам, чтение или запись сообщений в базу данных, постановка сообщений в очередь сообщений или удаление сообщений из очереди. Все эти действия имеют одну общую вещь: им требуется преобразование между экземпляром WCFMessage и фактической группой байтов, которые могут быть отправлены, получены, прочитаны, записаны, в очереди или отложены. Процесс преобразования 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(XmlWriter) для кодируемого сообщения, который записывает заголовки и текст сообщения с помощью WriteBodyContents(XmlDictionaryWriter)(см. пояснение в предыдущем подразделе данного раздела, Message ). Содержимое буфера (представленное в виде массива байтов) затем возвращается каналу транспорта для дальнейшего использования.
Кодирование (потоковое) При работе в потоковом режиме операция также выполняется согласно данному выше описанию, при этом ситуация упрощается тем, что нет необходимости использовать буфер. Как правило, средство записи XML создается над потоком, а метод WriteMessage(XmlWriter) вызывается для сообщения Message , чтобы записать его в это средство записи.
Декодирование (с буферизацией) При декодировании в режиме буферизации, как правило, создается специальный подкласс Message , который содержит буферизованные данные. Считываются заголовки сообщения, создается средство чтения XML и устанавливается в теле письма. Это средство чтения, которое будет возвращено с GetReaderAtBodyContents().
Декодирование (потоковое) При декодировании в потоковом режиме, как правило, создается специальный подкласс "Сообщения". Поток перемещается вперед ровно настолько, чтобы прочитать все заголовки и разместить их в теле сообщения. Затем над потоком создается средство чтения XML. Это средство чтения, которое будет возвращено с GetReaderAtBodyContents().

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

WCF предоставляет три кодировщика сообщений, хотя можно создать дополнительные пользовательские типы. Это текстовое и двоичное кодирование и механизм оптимизации передачи сообщений (MTOM). Эти типы подробно описаны в разделе Choosing a Message Encoder.

Интерфейс IStreamProvider

При записи исходящего сообщения с потоковым текстом в средство записи XML Message использует в реализации OnWriteBodyContents(XmlDictionaryWriter) последовательность вызовов, подобную описанной ниже:

  • Запись всех необходимых данных, предшествующих потоку (например, открывающий тег XML).

  • запись потока;

  • Запись данных, следующих за потоком (например, закрывающий тег XML).

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

Для этого используется интерфейс IStreamProvider . Этот интерфейс содержит метод GetStream() , который возвращает поток, подлежащий записи. Ниже описан правильный способ сохранения потокового тела сообщения в OnWriteBodyContents(XmlDictionaryWriter) .

  1. Запись всех необходимых данных, предшествующих потоку (например, открывающий тег XML).

  2. Вызов перегрузки WriteValue для XmlDictionaryWriter , принимающего IStreamProvider, с реализацией IStreamProvider , возвращающей подлежащий записи поток.

  3. Запись данных, следующих за потоком (например, закрывающий тег XML).

Используя этот подход, средство записи XML может выбирать, когда вызывать GetStream() и сохранять потоковые данные. Например, средства записи текстовых и двоичных данных XML немедленно вызывают его и записывают потоковое содержимое между открывающим и закрывающим тегами. Средство записи MTOM может вызвать GetStream() позже, когда будет готово к записи соответствующей части сообщения.

Представление данных в инфраструктуре службы

Как указано в разделе "Базовая архитектура" этого раздела, платформа служб является частью WCF, которая, помимо прочего, отвечает за преобразование между пользовательской моделью программирования для данных сообщений и фактических Message экземпляров. Как правило, обмен сообщениями представлен в платформе служб в виде метода платформа .NET Framework, помеченного атрибутомOperationContractAttribute. Метод может брать несколько параметров и возвращать возвращаемое значение или выходные параметры (или и то, и другое). На стороне службы входные параметры представляют входящее сообщение, а возвращаемое значение и выходные параметры - исходящее сообщение. На стороне клиента происходит обратное представление. Подробные сведения о модели программирования для описания сообщений с помощью параметров и возвращаемого значения см. в разделе Specifying Data Transfer in Service Contracts. А в этом разделе приводится краткий обзор моделей.

Модели программирования

Платформа служб WCF поддерживает пять различных моделей программирования для описания сообщений:

1. Пустое сообщение

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

[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer

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

[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)

Обратите внимание, что это описание отличается от контракта односторонних операций.

[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)

В примере SetDesiredTemperature описывается шаблон двустороннего обмена сообщениями. Сообщение возвращается от операции, но при этом оно пустое. От операции также можно возвратить ошибку. В примере "Set Lightbulb" описывается шаблон одностороннего обмена сообщениями, поэтому не требуется описание исходящего сообщения. В этом случае служба не может передать обратно клиенту какое-либо состояние.

2. Использование класса сообщений напрямую

Класс Message (или один из его подклассов) можно использовать напрямую в контракте операций. В этом случае инфраструктура службы только передает сообщение Message от операции стеку каналов и обратно без какой-либо дальнейшей обработки.

Существует два основных варианта непосредственного использования Message . Его можно использовать в продвинутых сценариях, если ни одна из других моделей программирования не является достаточно гибкой для описания данного сообщения. Например, для описания сообщения можно использовать файлы на диске, тогда свойства файла становятся заголовками сообщения, а его содержимое - текстом сообщения. В этом случае можно создать что-нибудь подобное описанному ниже.

public class FileMessage : Message
// Code not shown.
Public Class FileMessage
    Inherits Message
    ' Code not shown.

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

[OperationContract]
public FileMessage GetFile()
{
    //code omitted…
    FileMessage fm = new FileMessage("myFile.xml");
    return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
    'code omitted…
    Dim fm As New FileMessage("myFile.xml")
    Return fm
End Function

Строка Action="*" эффективно отключает отправку сообщений и гарантирует, что все сообщения, отправленные IForwardingService в контракт, делают свой путь к ForwardMessage операции. (Как правило, диспетчер проверяет заголовок сообщения "Действие", чтобы определить, для какой операции она предназначена. Action="*" означает "все возможные значения заголовка действия".) Сочетание Action="*" и использование сообщения в качестве параметра называется "универсальным контрактом", так как оно может получать все возможные сообщения. Чтобы иметь возможность отправлять все возможные сообщения, используйте сообщение в качестве возвращаемого значения и задайте ReplyAction значение "*". В этом случае инфраструктура службы не сможет добавить свой собственный заголовок действия, и вы сможете управлять этим заголовком с помощью возвращаемого вами объекта Message .

3. Контракты сообщений

WCF предоставляет декларативную модель программирования для описания сообщений, называемых контрактами сообщений. Эта модель более подробно описана в разделе Using Message Contracts. По сути, все сообщение представлено одним типом платформа .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;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
    <MessageHeader()> Public customerID As String
    <MessageBodyMember()> Public item As String
    <MessageBodyMember()> Public quantity As Integer
End Class

Элементы, отмеченные для сериализации (с атрибутами MessageBodyMemberAttribute, MessageHeaderAttributeили любыми другими связанными атрибутами), должны быть сериализуемыми, иначе они не могут находиться в контракте сообщения. Дополнительные сведения см. в разделе "Сериализация" далее в этом разделе.

4. Параметры

Часто для описания операции с несколькими элементами данных разработчику не требуется тот высокий уровень управления, который обеспечивается контрактами сообщений. Например, при создании новых служб, как правило, не приходится принимать решение о способе представления элементов (в режиме "bare" или в программе-оболочке) и выбирать имя программы-оболочки. Зачастую для принятия таких решений необходимы глубокие знания о функционировании веб-служб и протокола SOAP.

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

[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
    ByVal customerID As String, _
    ByVal item As String, _
    ByVal quantity As Integer)

Инфраструктура службы автоматически принимает решение о размещении всех трех элементов данных (customerID, itemи quantity) в теле сообщения и помещении их в программу-оболочку с именем SubmitOrderRequest.

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

5. Поток

Использование потока Stream или одного из его подклассов в контракте операций либо в качестве единственной части тела сообщения в контракте сообщений может считаться отдельной моделью программирования, которая отличается от описанных выше моделей. Только использование Stream подобным образом гарантирует возможность использования контракта в потоковом режиме; во всех остальных случаях необходимо самостоятельно создавать подкласс Message , совместимый с потоковым режимом. Дополнительные сведения см. в разделе "Большие данные" и "Потоковая передача".

При использовании Stream или одного из его подклассов таким образом сериализатор не вызывается. Для исходящих сообщений создается специальный потоковый подкласс Message , а поток записывается в интерфейсе IStreamProvider описанным в данном разделе способом. Для входящих сообщений инфраструктура службы создает подкласс Stream над входящими сообщениями и предоставляет этот подкласс операции.

Ограничения модели программирования

Вышеописанные модели программирования нельзя сочетать произвольно. Например, если операция принимает определенный тип контракта сообщений, этот контракт сообщений должен стать единственным входным параметром операции. Более того, после этого операция должна вернуть либо пустое сообщение (возвращаемый тип "void"), либо другой контракт сообщений. Ограничения моделей программирования описаны в разделах, посвященных каждой из этих моделей: Using Message Contracts, Using the Message Classи Large Data and Streaming.

Модули форматирования сообщений

Описанные выше модели программирования поддерживаются подключением компонентов под названием модули форматирования сообщений в инфраструктуру службы. Форматировщики сообщений — это типы, реализующие IClientMessageFormatter интерфейс или IDispatchMessageFormatter интерфейс, для использования в клиентах и клиентах WCF службы соответственно.

Как правило, модули форматирования сообщений подключаются поведениями. Например, поведение DataContractSerializerOperationBehavior подключает модуль форматирования сообщений контракта данных. На стороне службы это достигается благодаря установке Formatter на соответствующий модуль форматирования в методе ApplyDispatchBehavior(OperationDescription, DispatchOperation) , а на стороне клиента - благодаря установке Formatter на соответствующий модуль форматирования в методе ApplyClientBehavior(OperationDescription, ClientOperation) .

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

Интерфейс Способ Действие
IDispatchMessageFormatter DeserializeRequest(Message, Object[]) преобразует входящее сообщение Message в параметры операции
IDispatchMessageFormatter SerializeReply(MessageVersion, Object[], Object) создает исходящее сообщение Message из возвращаемого значения/выходных параметров операции
IClientMessageFormatter SerializeRequest(MessageVersion, Object[]) создает исходящее сообщение Message из параметров операции
IClientMessageFormatter DeserializeReply(Message, Object[]) преобразует входящее сообщение Message в возвращаемое значение/выходные параметры

Сериализация

При использовании контрактов сообщений или параметров для описания содержимого сообщения необходимо использовать сериализацию для преобразования между типами платформа .NET Framework и представлением XML-infoset. Сериализация используется в других местах в WCF, например Message , имеет универсальный GetBody метод, который можно использовать для чтения всего текста сообщения десериализации в объект.

WCF поддерживает две технологии сериализации "вне поля" для сериализации и десериализации параметров и частей сообщений: DataContractSerializer и .XmlSerializer Более того, можно создать настраиваемые сериализаторы. Однако другие части WCF (например, универсальный GetBody метод или сериализация ошибок SOAP) могут быть ограничены только XmlObjectSerializer подклассами (DataContractSerializer и NetDataContractSerializer, но не XmlSerializer), или даже могут быть жестко закодированы только для использования DataContractSerializer.

Это XmlSerializer модуль сериализации, используемый в веб-службах ASP.NET. Новый модуль сериализации DataContractSerializer , совместимый с новой моделью программирования на основе контрактов данных. DataContractSerializer выбирается по умолчанию, однако можно выбрать XmlSerializer для отдельных операций с помощью атрибута DataContractFormatAttribute .

DataContractSerializerOperationBehavior и XmlSerializerOperationBehavior - это поведения операции, ответственные за подключение модулей форматирования сообщений для DataContractSerializer и XmlSerializerсоответственно. Фактически, поведение DataContractSerializerOperationBehavior может работать с любым сериализатором, наследуемым от XmlObjectSerializer, включая NetDataContractSerializer (подробное описание см. в разделе "Использование автономной сериализации"). Поведение вызывает одну из перегрузок виртуального метода CreateSerializer для получения сериализатора. Для подключения иного сериализатора необходимо создать новый подкласс DataContractSerializerOperationBehavior и переопределить обе перегрузки метода CreateSerializer .

См. также