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

Класс является основным для Message Windows Communication Foundation (WCF). Все взаимодействие между клиентами и службами в конечном итоге приводит к отправке и получению экземпляров класса Message.

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

  • Если вам нужен альтернативный способ создания содержимого исходящего сообщения (например, создание сообщения непосредственно из файла на диске) вместо сериализации объектов платформа .NET Framework.

  • Если вам нужен альтернативный способ использования входящего содержимого сообщения (например, при применении преобразования XSLT к необработанному содержимому XML) вместо десериализации в объекты платформа .NET Framework.

  • Если необходимо совершить общие операции с сообщениями независимо от их содержимого (например, маршрутизировать или переслать сообщения при создании маршрутизатора, подсистемы балансировки нагрузки или системы публикации-подписки).

Прежде чем использовать Message класс, ознакомьтесь с архитектурой передачи данных WCF в обзоре архитектуры передачи данных.

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

Использование класса сообщений в операциях

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

  • Операция не должна иметь параметров out или ref.

  • Операция не должна иметь более одного параметра input. Если параметр присутствует, это должен быть параметр Message или тип контракта сообщений.

  • Возвращаемый тип должен представлять собой void, Message или тип контракта сообщений.

В следующем примере кода показан действительный контракт операции.

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    Message GetData();

    [OperationContract]
    void PutData(Message m);
}
<ServiceContract()> _
Public Interface IMyService
    <OperationContract()> _
    Function GetData() As Message

    <OperationContract()> _
    Sub PutData(ByVal m As Message)
End Interface

Создание базовых сообщений

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

Все перегрузки CreateMessage могут принимать параметр версии типа MessageVersion, который указывает, какие версии протокола SOAP и WS-Addressing использовать для данного сообщения. При необходимости использовать те же версии протокола, что и входящее сообщение, можно использовать свойство IncomingMessageVersion в экземпляре OperationContext, полученном из свойства Current. Большинство перегрузок CreateMessage также имеют параметр строки, который указывает, какое действие SOAP следует использовать для сообщения. Чтобы отключить создание конверта SOAP, можно задать версии значение None, тогда сообщение будет состоять только из тела.

Создание сообщений из объектов

Самая обычная перегрузка метода CreateMessage, использующая только версию и действие, создает сообщение с пустым телом. Перегрузка, принимающая дополнительный параметр Object, создает сообщение, тело которого представляет собой сериализуемое представление данного объекта. Для сериализации используйте сериализатор DataContractSerializer с параметрами по умолчанию. При необходимости использовать другой сериализатор или сериализатор DataContractSerializer с другими настройками используйте перегрузку метода CreateMessage, которая также принимает параметр XmlObjectSerializer.

Например, чтобы вернуть объект в сообщение, следует воспользоваться следующим кодом.

public class MyService1 : IMyService
{
    public Message GetData()
    {
        Person p = new Person();
        p.name = "John Doe";
        p.age = 42;
        MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
        return Message.CreateMessage(ver, "GetDataResponse", p);
    }

    public void PutData(Message m)
    {
        // Not implemented.
    }
}
[DataContract]
public class Person
{
    [DataMember] public string name;
    [DataMember] public int age;
}
Public Class MyService1
    Implements IMyService

    Public Function GetData() As Message _
     Implements IMyService.GetData
        Dim p As New Person()
        p.name = "John Doe"
        p.age = 42
        Dim ver As MessageVersion = _
          OperationContext.Current.IncomingMessageVersion
        Return Message.CreateMessage(ver, "GetDataResponse", p)

    End Function


    Public Sub PutData(ByVal m As Message) _
    Implements IMyService.PutData
        ' Not implemented.
    End Sub
End Class
<DataContract()> _
Public Class Person
    <DataMember()> _
    Public name As String
    <DataMember()> _
    Public age As Integer
End Class

Создание сообщений из средств чтения XML

Существуют перегрузки метода CreateMessage, которые вместо объекта принимают средство чтения XmlReader или XmlDictionaryReader для тела сообщения. В этом случае тело сообщения содержит XML-код, создаваемый в результате чтения из переданного средства чтения XML. Например, следующий код возвращает сообщение, содержимое тела которого прочитано из файла XML.

public class MyService2 : IMyService
{
    public Message GetData()
    {
        FileStream stream = new FileStream("myfile.xml",FileMode.Open);
        XmlDictionaryReader xdr =
               XmlDictionaryReader.CreateTextReader(stream,
                           new XmlDictionaryReaderQuotas());
        MessageVersion ver =
            OperationContext.Current.IncomingMessageVersion;
        return Message.CreateMessage(ver,"GetDataResponse",xdr);
    }

    public void PutData(Message m)
    {
        // Not implemented.
    }
}
Public Class MyService2
    Implements IMyService

    Public Function GetData() As Message Implements IMyService.GetData
        Dim stream As New FileStream("myfile.xml", FileMode.Open)
        Dim xdr As XmlDictionaryReader = _
        XmlDictionaryReader.CreateTextReader(stream, New XmlDictionaryReaderQuotas())
        Dim ver As MessageVersion = OperationContext.Current.IncomingMessageVersion
        Return Message.CreateMessage(ver, "GetDataResponse", xdr)

    End Function


    Public Sub PutData(ByVal m As Message) Implements IMyService.PutData

    End Sub
End Class

Кроме того, существуют перегрузки метода CreateMessage, которые принимают средство чтения XmlReader или XmlDictionaryReader, представляющее все сообщение, а не только его тело. Эти перегрузки также принимают целочисленный параметр maxSizeOfHeaders. Заголовки всегда буферизуются в память сразу после создания сообщения, а этот параметр ограничивает объем буферизации. Для снижения вероятности атаки типа "отказ в обслуживании" необходимо задать безопасное значение этому параметру, если XML-код поступает из ненадежного источника. Версии SOAP и WS-Addressing сообщения, представляемого средством чтения XML, должны соответствовать версиям, указанным с использованием параметра версии.

Создание сообщений с помощью конструктора BodyWriter

Одна из перегрузок метода CreateMessage использует экземпляр BodyWriter для описания тела сообщения. BodyWriter - это абстрактный класс, который может наследоваться для настройки способа создания тел сообщений. Можно создать собственный производный класс BodyWriter для описания тел сообщений пользовательским способом. Необходимо переопределить метод BodyWriter.OnWriteBodyContents, принимающий XmlDictionaryWriter; этот метод используется для записи тела сообщения.

Некоторые средства записи тела сообщения используют буферизацию, другие (потоковые) - нет. Средства записи тела с буферизацией могут записывать содержимое неограниченное число раз, а потоковые - только один раз. Свойство IsBuffered указывает, использует ли средство записи тела сообщения буферизацию или нет. Настроить это свойство для используемого средства записи тела сообщения можно вызовом защищенного конструктора BodyWriter, принимающего логический параметр isBuffered. Средства записи тела сообщения позволяют создавать средство записи тела сообщения с буферизацией из средства записи без буферизации. Для настройки этого процесса можно переопределить метод OnCreateBufferedCopy. По умолчанию используется буфер в памяти, который содержит XML-код, возвращаемый OnWriteBodyContents. OnCreateBufferedCopy принимает целочисленный параметр maxBufferSize; при переопределении этого метода не следует создавать буферы, размер которых превышает данное максимальное значение.

Класс BodyWriter предоставляет методы WriteBodyContents и CreateBufferedCopy, которые, по сути, являются тонкими программами-оболочками для методов OnWriteBodyContents и OnCreateBufferedCopy соответственно. Эти методы выполняют проверку состояния, чтобы убедиться, что доступ к средству записи тела сообщения без буферизации не осуществляется более одного раза. Эти методы вызываются непосредственно только при создании пользовательских производных классов Message на основе BodyWriters.

Создание сообщений об ошибках

Для создания сообщений об ошибке SOAP можно использовать определенные перегрузки CreateMessage. Основная из них принимает объект MessageFault, который описывает ошибку. Другие перегрузки предоставляются для удобства. Первая такая перегрузка принимает FaultCode и строку причины и создает MessageFault с помощью MessageFault.CreateFault, использующего эти сведения. Другая перегрузка этого метода принимает объект сведений и передает его CreateFault вместе с кодом ошибки и причиной. Например, следующая операция возвращает ошибку.

public class MyService3 : IMyService
{
    public Message GetData()
    {
        FaultCode fc = new FaultCode("Receiver");
        MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
            return Message.CreateMessage(ver,fc,"Bad data","GetDataResponse");
    }

    public void PutData(Message m)
    {
        // Not implemented.
    }
}
Public Class MyService3
    Implements IMyService

    Public Function GetData() As Message Implements IMyService.GetData
        Dim fc As New FaultCode("Receiver")
        Dim ver As MessageVersion = OperationContext.Current.IncomingMessageVersion
        Return Message.CreateMessage(ver, fc, "Bad data", "GetDataResponse")

    End Function


    Public Sub PutData(ByVal m As Message) Implements IMyService.PutData

    End Sub
End Class

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

Класс Message позволяет извлекать данные из его тела несколькими способами. Эти способы можно разделить на следующие категории.

  • Возвращение целого тела сообщения, мгновенно записываемого в средство записи XML. Это называется записью сообщения.

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

  • Все сообщение, включая его тело, можно скопировать в буфер памяти типа MessageBuffer. Это называется копированием сообщения.

Доступ к телу Message можно получить только один раз независимо от того, каким способом осуществляется доступ. Объект сообщения имеет свойство State, которому изначально задано значение Created. Три способа доступа, описанные в вышеприведенном списке, устанавливают состояние Written, Read и Copied соответственно. Кроме того, метод Close может установить состояние Closed, если содержимое тела сообщения больше не требуется. Доступ к телу сообщения осуществляется только в состоянии Created, вернуться к состоянию Created после изменения состояния невозможно.

Создание сообщений

Метод WriteBodyContents(XmlDictionaryWriter) записывает содержимое тела заданного экземпляра Message в заданное средство записи XML. Метод WriteBody выполняет то же самое, за исключением того, что он заключает содержимое тела в соответствующий элемент оболочки (например,soap:body<> ). Наконец, метод WriteMessage записывает все сообщение, включая конверт SOAP и заголовки, выполняющие функции оболочки. Если протокол SOAP отключен (Version есть MessageVersion.None), все три метода выполняют одно и то же: они записывают содержимое текста сообщения.

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

public class MyService4 : IMyService
{
    public void PutData(Message m)
    {
        FileStream stream = new FileStream("myfile.xml",FileMode.Create);
        XmlDictionaryWriter xdw =
            XmlDictionaryWriter.CreateTextWriter(stream);
        m.WriteBodyContents(xdw);
        xdw.Flush();
    }

    public Message GetData()
    {
        throw new NotImplementedException();
    }
}
Public Class MyService4
    Implements IMyService

    Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
        Dim stream As New FileStream("myfile.xml", FileMode.Create)
        Dim xdw As XmlDictionaryWriter = XmlDictionaryWriter.CreateTextWriter(stream)
        m.WriteBodyContents(xdw)
        xdw.Flush()

    End Sub


    Public Function GetData() As Message Implements IMyService.GetData
        Throw New NotImplementedException()

    End Function
End Class

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

  • WriteStartBody записывает начальный элемент тела, например <soap:Body>.

  • WriteStartEnvelope записывает начальный элемент конверта, например <soap:Envelope>.

Для записи соответствующих тегов конечных элементов необходимо вызвать метод WriteEndElement в соответствующем средстве записи XML. Эти методы редко вызываются напрямую.

Чтение сообщений

Основной способ прочитать тело сообщения - это вызвать метод GetReaderAtBodyContents. В результате возвращается средство чтения XmlDictionaryReader, которое можно использовать для чтения тела сообщения. Обратите внимания, что Message переходит в состояние Read сразу после вызова GetReaderAtBodyContents, а не одновременно с использованием возвращенного средства чтения XML.

Метод GetBody также позволяет получать доступ к телу сообщения как типизированному объекту. Изнутри этот метод использует GetReaderAtBodyContents, поэтому он также изменяет состояние сообщения на Read (см. свойство State).

Рекомендуется всегда проверять свойство IsEmpty; в этом случае тело сообщения пустое, а GetReaderAtBodyContents создает исключение InvalidOperationException. Кроме того, если речь идет о полученном сообщении (например, ответе), целесообразно проверить свойство IsFault, указывающее, содержит ли сообщение ошибку.

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

Например, следующий код извлекает данные из тела сообщения, которое содержит сериализованный объект Person и выводит имя пользователя.

    public class MyService5 : IMyService
    {
        public void PutData(Message m)
        {
            Person p = m.GetBody<Person>();
            Console.WriteLine(p.name);
        }

        public Message GetData()
        {
            throw new NotImplementedException();
        }
    }
}
namespace Samples2
{
    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        Message GetData();

        [OperationContract]
        void PutData(Message m);
    }

    [DataContract]
    public class Person
    {
        [DataMember] public string name;
        [DataMember] public int age;
    }
    Public Class MyService5
        Implements IMyService

        Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
            Dim p As Person = m.GetBody(Of Person)()
            Console.WriteLine(p.name)

        End Sub


        Public Function GetData() As Message Implements IMyService.GetData
            Throw New NotImplementedException()

        End Function
    End Class
End Namespace
Namespace Samples2
    <ServiceContract()> _
    Public Interface IMyService
        <OperationContract()> _
        Function GetData() As Message

        <OperationContract()> _
        Sub PutData(ByVal m As Message)
    End Interface

    <DataContract()> _
    Public Class Person
        <DataMember()> _
        Public name As String
        <DataMember()> _
        Public age As Integer
    End Class

Копирование сообщения в буфер

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

Буфер возвращается в виде экземпляра MessageBuffer. Доступ к данным в буфере осуществляется несколькими способами. Основной способ - это вызов метода CreateMessage для создания экземпляров Message из буфера.

Доступ к данным в буфере также можно получить реализацией интерфейса IXPathNavigable, который класс MessageBuffer реализует для осуществления прямого доступа к лежащему в основе XML-коду. Некоторые перегрузки метода CreateNavigator позволяют создавать навигаторы System.Xml.XPath, защищенные квотой узла, которая ограничивает число узлов XML для посещения. Это позволяет предотвратить атаки типа "отказ в обслуживании", возникающие из-за длительного времени обработки. По умолчанию эта квота выключена. Некоторые перегрузки метода CreateNavigator позволяют задавать, как следует обрабатывать пробелы в XML с использованием перечисления XmlSpace, по умолчанию установлено значение XmlSpace.None.

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

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

[ServiceContract]
public class ForwardingService
{
    private List<IOutputChannel> forwardingAddresses;

    [OperationContract]
    public void ForwardMessage (Message m)
    {
        //Copy the message to a buffer.
        MessageBuffer mb = m.CreateBufferedCopy(65536);

        //Forward to multiple recipients.
        foreach (IOutputChannel channel in forwardingAddresses)
        {
            Message copy = mb.CreateMessage();
            channel.Send(copy);
        }

        //Log to a file.
        FileStream stream = new FileStream("log.xml",FileMode.Append);
        mb.WriteMessage(stream);
        stream.Flush();
    }
}
<ServiceContract()> _
Public Class ForwardingService
    Private forwardingAddresses As List(Of IOutputChannel)

    <OperationContract()> _
    Public Sub ForwardMessage(ByVal m As Message)
        'Copy the message to a buffer.
        Dim mb As MessageBuffer = m.CreateBufferedCopy(65536)

        'Forward to multiple recipients.
        Dim channel As IOutputChannel
        For Each channel In forwardingAddresses
            Dim copy As Message = mb.CreateMessage()
            channel.Send(copy)
        Next channel

        'Log to a file.
        Dim stream As New FileStream("log.xml", FileMode.Append)
        mb.WriteMessage(stream)
        stream.Flush()

    End Sub
End Class

Имеет смысл упомянуть и о других членах класса MessageBuffer. Метод Close можно вызвать для освобождения ресурсов, когда содержимое буфера больше не требуется. Свойство BufferSize возвращает размер выделенного буфера. Свойство MessageContentType возвращает тип содержимого сообщения MIME.

Доступ к телу сообщения для отладки

При отладке можно вызвать метод ToString, чтобы представить сообщение в качестве строки. Это представление, как правило, соответствует виду кодированного с помощью текстового кодировщика сообщения с той разницей, что XML лучше отформатирован для восприятия человеком. Единственным исключением из вышесказанного является тело сообщения. Тело сообщения можно прочитать только один раз, и ToString не меняет состояние сообщения. Таким образом, ToString метод может не иметь доступа к тексту и может заменить заполнитель (например, "..." или три точки) вместо текста сообщения. Следовательно, не рекомендуется использовать ToString для записи сообщений в журнал, если важно содержимое тел сообщений.

Осуществление доступа к другим частям сообщения

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

  • Свойство Headers представляет заголовки сообщения. См. раздел "Работа с заголовками" далее в этом разделе.

  • Свойство Properties представляет свойства сообщения, которые являются элементами именованных данных, прикрепленных к сообщению; как правило, они не выдаются при отправке сообщения. См. подраздел "Работа со свойствами" далее в этом разделе.

  • Свойство Version указывает на версию SOAP и WS-Addressing, связанную с сообщением, или имеет значение None, если SOAP отключен.

  • Свойство IsFault возвращает значение true, если сообщение является сообщением об ошибке SOAP.

  • Свойство IsEmpty возвращает значение true, если сообщение пустое.

Для доступа к определенному атрибуту в программе-оболочке тела (например, GetBodyAttribute(String, String)), обозначаемому определенным именем и пространством имен, можно использовать метод <soap:Body>. Если такой атрибут не найден, возвращается значение null. Этот метод можно вызвать, только если сообщение Message находится в состоянии Created (если доступ к телу сообщения еще не осуществлялся).

Работа с заголовками

Может Message содержать любое число именованных ФРАГМЕНТов XML, называемых заголовками. Как правило, каждый фрагмент сопоставляется заголовку SOAP. Доступ к заголовкам осуществляется через свойство Headers типа MessageHeaders. MessageHeaders - это коллекция объектов MessageHeaderInfo. Доступ к отдельным заголовкам осуществляется через его интерфейс IEnumerable или индексатор. Например, в следующем коде перечислены имена всех заголовков в сообщении Message.

public class MyService6 : IMyService
{
    public void PutData(Message m)
    {
        foreach (MessageHeaderInfo mhi in m.Headers)
        {
            Console.WriteLine(mhi.Name);
        }
    }

    public Message GetData()
    {
        throw new NotImplementedException();
    }
}
Public Class MyService6
    Implements IMyService

    Public Sub PutData(ByVal m As Message) Implements IMyService.PutData
        Dim mhi As MessageHeaderInfo
        For Each mhi In m.Headers
            Console.WriteLine(mhi.Name)
        Next mhi

    End Sub


    Public Function GetData() As Message Implements IMyService.GetData
        Throw New NotImplementedException()

    End Function
End Class

Добавление, удаление, нахождение заголовков

С помощью метода Add можно добавлять новый заголовок в конце всех существующих заголовков. Для вставки заголовка в заданном индексе можно использовать метод Insert. Существующие заголовки сдвигаются на вставленный элемент. Заголовки упорядочиваются по индексу, первым доступным индексом является 0. Можно использовать различные методы перегрузки CopyHeadersFrom для добавления заголовков из другого экземпляра Message или MessageHeaders. Некоторые перегрузки копируют только один заголовок, другие копируют все заголовки. Метод Clear удаляет все заголовки. Метод RemoveAt удаляет заголовок в определенном индексе (сдвигая все заголовки после него). Метод RemoveAll удаляет все заголовки с определенным именем и пространством имен.

Метод FindHeader позволяет извлечь определенный заголовок. Этот метод использует для нахождения заголовка его имя и пространство имен и возвращает его индекс. Если заголовок встречается более одного раза, создается исключение. Если заголовок не найден, возвращается -1.

В модели заголовков SOAP заголовки могут иметь значение Actor, которое задает законного получателя заголовка. Основная перегрузка метода FindHeader осуществляет только поиск заголовков, предназначенных для конечного получателя сообщения. Другая перегрузка метода позволяет указать, какие значения Actor необходимо включить в поиск. Дополнительные сведения см. в спецификации SOAP.

Метод CopyTo(MessageHeaderInfo[], Int32) предоставляется для копирования заголовков из коллекции MessageHeaders в массив объектов MessageHeaderInfo.

Чтобы получить доступ к XML-данным в заголовке, можно вызвать метод GetReaderAtHeader и вернуть средство чтения XML для конкретного индекса заголовка. При необходимости десериализовать содержимое заголовка в объект, следует использовать GetHeader<T>(Int32) или любую другую перегрузку. Основные перегрузки десериализуют заголовки с помощью сериализатора DataContractSerializer, настроенного по умолчанию. При необходимости использовать другой сериализатор или сериализатор DataContractSerializer с другими настройками используйте одну из перегрузок, принимающих XmlObjectSerializer. Существуют перегрузки, принимающие вместо индекса имя заголовка, пространство имен и дополнительно список значений Actor; в этом случае получается сочетание FindHeader и GetHeader.

Работа со свойствами

Экземпляр Message может содержать произвольное число именованных объектов произвольных типов. Доступ к этой коллекции осуществляется через свойство Properties типа MessageProperties. Коллекция реализует интерфейс IDictionary<TKey,TValue> и действует как сопоставление между String и Object. Как правило, значения свойств не сопоставляются непосредственно с какой-либо частью сообщения в проводной сети, а не предоставляют различные указания по обработке сообщений с различными каналами в стеке каналов WCF или с платформой CopyTo(MessageHeaderInfo[], Int32) служб. Пример см. в разделе "Обзор архитектуры передачи данных".

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

Если встроенные типы сообщений, созданные с использованием CreateMessage, не соответствуют предъявляемым требованиям, можно создать класс, который наследуется от класса Message.

Определение содержимого тела сообщения

Для осуществления доступа к данным в теле сообщения существует три основных методики: запись, чтение и копирование в буфер. Эти операции, по сути, сводятся к вызову для производного класса OnWriteBodyContents методов OnGetReaderAtBodyContents, OnCreateBufferedCopy и Message соответственно. Базовый класс Message гарантирует, что для каждого экземпляра Message вызывается только один из этих методов и вызывается однократно. Базовый класс также гарантирует, что эти методы не вызываются для закрытого сообщения. Нет необходимости отслеживать состояние сообщения в реализации.

Метод OnWriteBodyContents является абстрактным и должен быть реализован. Основным способом определения содержимого тела сообщения является его запись с помощью этого метода. Например, в следующем сообщении содержится 100 000 случайных чисел от 1 до 20.

public class RandomMessage : Message
{
    override protected  void  OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        Random r = new Random();
        for (int i = 0; i <100000; i++)
        {
            writer.WriteStartElement("number");
            writer.WriteValue(r.Next(1,20));
            writer.WriteEndElement();
        }
    }
    //code omitted…
Public Class RandomMessage
    Inherits Message

    Protected Overrides Sub OnWriteBodyContents( _
            ByVal writer As XmlDictionaryWriter)
        Dim r As New Random()
        Dim i As Integer
        For i = 0 To 99999
            writer.WriteStartElement("number")
            writer.WriteValue(r.Next(1, 20))
            writer.WriteEndElement()
        Next i

    End Sub
    ' Code omitted.

Методы OnGetReaderAtBodyContents() и OnCreateBufferedCopy имеют реализации по умолчанию, которые срабатывают в большинстве случаев. Реализации по умолчанию вызывают метод OnWriteBodyContents, буферизуют результаты и работают с получившимся буфером. Однако в некоторых случаях этого недостаточно. В предыдущем примере чтение сообщения приводит к буферизации 100 000 элементов XML, что может оказаться нежелательным. Возможно, для возвращения пользовательского производного класса OnGetReaderAtBodyContents(), обслуживающего случайные числа, целесообразно переопределить метод XmlDictionaryReader. Затем можно переопределить OnWriteBodyContents использование средства чтения, возвращаемого OnGetReaderAtBodyContents() методом, как показано в следующем примере.

    public override MessageHeaders Headers
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MessageProperties Properties
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MessageVersion Version
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }
}

public class RandomMessage2 : Message
{
    override protected XmlDictionaryReader OnGetReaderAtBodyContents()
    {
    return new RandomNumbersXmlReader();
    }

    override protected void OnWriteBodyContents(XmlDictionaryWriter writer)
    {
        XmlDictionaryReader xdr = OnGetReaderAtBodyContents();
        writer.WriteNode(xdr, true);
    }
    public override MessageHeaders Headers
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MessageProperties Properties
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MessageVersion Version
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }
}

public class RandomNumbersXmlReader : XmlDictionaryReader
{
    //code to serve up 100000 random numbers in XML form omitted…

    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 Properties() As MessageProperties
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property

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

Public Class RandomMessage2
    Inherits Message

    Protected Overrides Function OnGetReaderAtBodyContents() As XmlDictionaryReader
        Return New RandomNumbersXmlReader()

    End Function


    Protected Overrides Sub OnWriteBodyContents(ByVal writer As XmlDictionaryWriter)
        Dim xdr As XmlDictionaryReader = OnGetReaderAtBodyContents()
        writer.WriteNode(xdr, True)

    End Sub

    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 Properties() As MessageProperties
        Get
            Throw New Exception("The method or operation is not implemented.")
        End Get
    End Property

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

Public Class RandomNumbersXmlReader
    Inherits XmlDictionaryReader
    'code to serve up 100000 random numbers in XML form omitted

Аналогично, для возвращения собственного производного класса OnCreateBufferedCopy имеет смысл переопределить метод MessageBuffer.

Производный класс сообщения должен не только предоставлять содержимое тела сообщения, но и переопределять свойства Version, Headers и Properties.

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

Другие переопределяемые члены

Можно переопределить OnWriteStartEnvelopeOnWriteStartHeadersOnWriteStartBody и методы, чтобы указать способ записи тегов начального элемента SOAP, конверта SOAP, заголовков SOAP и элемента текста SOAP. Обычно они соответствуют <soap:Envelope>, <soap:Header>и <soap:Body>. Если свойство Version возвращает значение None, как правило, эти методы ничего не записывают.

Примечание.

До вызова метода OnGetReaderAtBodyContents и буферизации результатов используемая по умолчанию реализация OnWriteStartEnvelope вызывает методы OnWriteStartBody и OnWriteBodyContents. Заголовки не записываются.

Чтобы изменить способ создания целого сообщения из различных элементов, необходимо переопределить метод OnWriteMessage. Метод OnWriteMessage вызывается из WriteMessage и реализации по умолчанию OnCreateBufferedCopy. Обратите внимание, что переопределять WriteMessage не рекомендуется. Вместо этого целесообразнее переопределить соответствующие методы On (например, OnWriteStartEnvelope, OnWriteStartHeaders и OnWriteBodyContents).

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

Чтобы разрешить доступ к атрибутам в элементе тела сообщения SOAP, нужно переопределить метод OnGetBodyAttribute. Этот метод можно вызывать неограниченное количество раз, но базовый тип сообщения Message гарантирует, что этот метод вызывается, только если сообщение находится в состоянии Created. В реализации не требуется проверять состояние сообщения. Реализация по умолчанию всегда возвращает значение null, что указывает на отсутствие атрибутов в элементе тела сообщения.

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

Свойства IsEmpty и IsFault могут переопределяться. По умолчанию оба свойства возвращают false.