Usando a classe de mensagens

A classe Message é fundamental para o WCF (Windows Communication Foundation). Toda a comunicação entre clientes e serviços, em última análise, resulta em instâncias Message sendo enviadas e recebidas.

Normalmente, você não interagiria diretamente com a classe Message. Em vez disso, as construções do modelo de serviço de aplicativo do WFC (Windows Communication Foundation), como contratos de dados, contratos de mensagens e contratos de operação, são usadas para descrever mensagens de entrada e saída. No entanto, em alguns cenários avançados, você pode programar usando a classe Message diretamente. Por exemplo, pode ser que você queira alterar a classe Message:

  • Quando você precisa de uma maneira alternativa de criar conteúdo de mensagem de saída (por exemplo, criar uma mensagem diretamente de um arquivo em disco) em vez de serializar objetos do .NET Framework.

  • Quando você precisa de uma maneira alternativa de usar o conteúdo da mensagem de entrada (por exemplo, quando deseja aplicar uma transformação XSLT ao conteúdo XML bruto) em vez de desserializar em objetos do .NET Framework.

  • Quando você precisa lidar com mensagens de maneira geral, independentemente do conteúdo da mensagem (por exemplo, ao rotear ou encaminhar mensagens ao criar um roteador, um balanceador de carga ou um sistema de publicação de assinatura).

Antes de usar a classe Message, familiarize-se com a arquitetura de transferência de dados do WCF na Visão geral da arquitetura de transferência de dados.

Um Message é um contêiner de uso geral para dados, mas seu design segue de perto o design de uma mensagem no protocolo SOAP. Assim como no SOAP, uma mensagem tem um corpo de mensagem e cabeçalhos. O corpo da mensagem contém os dados de carga reais, enquanto os cabeçalhos contêm contêineres de dados nomeados adicionais. As regras para ler e gravar o corpo e os cabeçalhos são diferentes, por exemplo, os cabeçalhos são sempre armazenados em buffer na memória e podem ser acessados em qualquer ordem várias vezes, enquanto o corpo pode ser lido apenas uma vez e pode ser transmitido. Normalmente, ao usar SOAP, o corpo da mensagem é mapeado para o corpo SOAP e os cabeçalhos de mensagem são mapeados para os cabeçalhos SOAP.

Usando a classe de mensagem em operações

Você pode usar a classe Message como um parâmetro de entrada de uma operação, o valor retornado de uma operação ou ambos. Ao usar Message em qualquer lugar em uma operação, as seguintes restrições se aplicarão:

  • A operação não pode ter nenhum parâmetro out ou ref.

  • Não pode haver mais de um input parâmetro. Se o parâmetro estiver presente, ele deverá ser uma mensagem ou um tipo de contrato de mensagem.

  • O tipo de retorno deve ser void, Messageou um tipo de contrato de mensagem.

O exemplo de código a seguir contém um contrato de operação válido.

[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

Criando mensagens básicas

A classe Message fornece métodos de fábrica estáticos CreateMessage que você pode usar para criar mensagens básicas.

Todas as sobrecargas CreateMessage usam um parâmetro de versão do tipo MessageVersion que indica as versões SOAP e WS-Addressing a serem usadas para a mensagem. Se você quiser usar as mesmas versões de protocolo que a mensagem de entrada, poderá usar a propriedade IncomingMessageVersion na instância OperationContext obtida da propriedade Current. A maioria das sobrecargas CreateMessage também tem um parâmetro de cadeia de caracteres que indica a ação SOAP a ser usada para a mensagem. A versão pode ser definida para desabilitar a geração None de envelope SOAP; a mensagem consiste apenas no corpo.

Criando mensagens com base em objetos

A sobrecarga mais básica CreateMessage que usa apenas uma versão e uma ação cria uma mensagem com um corpo vazio. Outra sobrecarga usa um parâmetro adicional Object; isso cria uma mensagem cujo corpo é a representação serializada do objeto fornecido. Use as configurações DataContractSerializer com padrão para serialização. Se você quiser usar um serializador diferente ou quiser DataContractSerializer configurado de forma diferente, use a sobrecarga CreateMessage que também usa um parâmetro XmlObjectSerializer.

Por exemplo, para retornar um objeto em uma mensagem, você pode usar o código a seguir.

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

Criando mensagens de leitores XML

Há sobrecargas CreateMessage que levam um XmlReader ou um XmlDictionaryReader para o corpo em vez de um objeto. Nesse caso, o corpo da mensagem contém o XML que resulta da leitura do leitor XML passado. Por exemplo, o código a seguir retorna uma mensagem com o conteúdo do corpo lido de um arquivo 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

Além disso, há CreateMessage sobrecargas que levam um XmlReader ou um XmlDictionaryReader, representando toda a mensagem e não apenas o corpo. Essas sobrecargas também levam um parâmetro inteiro maxSizeOfHeaders. Os cabeçalhos são sempre armazenados em buffer na memória assim que a mensagem é criada, e esse parâmetro limita a quantidade de buffers que ocorre. É importante definir esse parâmetro como um valor seguro se o XML for proveniente de uma fonte não confiável para atenuar a possibilidade de um ataque de negação de serviço. As versões SOAP e WS-Addressing da mensagem que o leitor XML representa devem corresponder às versões indicadas usando o parâmetro de versão.

Criando mensagens com BodyWriter

Uma sobrecarga CreateMessage usa uma instância BodyWriter para descrever o corpo da mensagem. Uma BodyWriter é uma classe abstrata que pode ser derivada para personalizar como os corpos de mensagem são criados. Você pode criar sua própria classe derivada BodyWriter para descrever corpos de mensagens de forma personalizada. Você deve substituir o método BodyWriter.OnWriteBodyContents que usa um XmlDictionaryWriter; este método é responsável por escrever o corpo.

Os gravadores de corpo podem ser armazenados em buffer ou não armazenados em buffer (transmitidos). Os gravadores de corpo em buffer podem gravar seu conteúdo várias vezes, enquanto os transmitidos podem gravar seu conteúdo apenas uma vez. A propriedade IsBuffered indica se um gravador de corpo está em buffer ou não. Você pode definir isso para o gravador de corpo chamando o construtor protegido BodyWriter que usa um parâmetro booliano isBuffered. Os gravadores de corpo dão suporte à criação de um gravador de corpo em buffer de um gravador de corpo não armazenado em buffer. Você pode substituir o método OnCreateBufferedCopy para personalizar esse processo. Por padrão, um buffer na memória que contém o XML retornado por OnWriteBodyContents é usado. OnCreateBufferedCopy usa um maxBufferSize parâmetro inteiro; se você substituir esse método, não deverá criar buffers maiores que esse tamanho máximo.

A classe BodyWriter fornece os métodos WriteBodyContents e CreateBufferedCopy que são essencialmente wrappers finos ao redor dos métodos OnWriteBodyContents e OnCreateBufferedCopy, respectivamente. Esses métodos executam a verificação de estado para garantir que um gravador de corpo não armazenado em buffer não seja acessado mais de uma vez. Esses métodos são chamados diretamente somente ao criar classes derivadas personalizadas Message com base em BodyWriters.

Criando mensagens de falha

Você pode usar determinadas sobrecargas CreateMessage para criar mensagens de falha SOAP. O mais básico deles usa um objeto MessageFault que descreve a falha. Outras sobrecargas são fornecidas para conveniência. A primeira sobrecarga desse tipo usa uma cadeia de caracteres FaultCode e um motivo e cria um MessageFault usando MessageFault.CreateFault e essas informações. A outra sobrecarga pega um objeto de detalhe e também o passa para CreateFault com o código de falha e o motivo. Por exemplo, a operação a seguir retorna uma falha.

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

Extraindo dados do corpo da mensagem

A classe Message dá suporte a várias maneiras de extrair informações de seu corpo. Elas podem ser classificadas nas seguintes categorias:

  • Colocar todo o corpo da mensagem gravado de uma vez em um gravador XML. Isso é conhecido como escrever uma mensagem.

  • Obtendo um leitor XML sobre o corpo da mensagem. Isso permite que você acesse posteriormente o corpo da mensagem peça a peça, conforme necessário. Isso é conhecido como leitura de uma mensagem.

  • Toda a mensagem, incluindo seu corpo, pode ser copiada para um buffer na memória do tipo MessageBuffer. Isso é conhecido como cópia de uma mensagem.

Você pode acessar o corpo de Message apenas uma vez, independentemente de como ele é acessado. Um objeto de mensagem tem uma propriedade State, que é inicialmente definida como Created. Os três métodos de acesso descritos na lista anterior definem o estado como Escrito, Lido e Copiado, respectivamente. Além disso, um método Close pode definir o estado como Fechado quando o conteúdo do corpo da mensagem não for mais necessário. O corpo da mensagem só pode ser acessado no estado Criado e não há como voltar para o estado Criado depois que o estado for alterado.

Escrevendo mensagens

O método WriteBodyContents(XmlDictionaryWriter) grava o conteúdo do corpo de uma determinada instância Message em um determinado gravador XML. O método WriteBody faz o mesmo, exceto que ele inclui o conteúdo do corpo no elemento wrapper apropriado (por exemplo, <soap:body> ). Por fim, WriteMessage grava toda a mensagem, incluindo o envelope SOAP de encapsulamento e os cabeçalhos. Se SOAP estiver desativado (Version é MessageVersion.None), todos os três métodos farão a mesma coisa: eles gravam o conteúdo do corpo da mensagem.

Por exemplo, o código a seguir grava o corpo de uma mensagem de entrada em um arquivo.

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

Dois métodos auxiliares adicionais gravam determinadas marcas de elemento SOAP inicial. Esses métodos não acessam o corpo da mensagem e, portanto, não alteram o estado da mensagem. Estão incluídos:

  • WriteStartBody grava o elemento de corpo inicial, por exemplo, <soap:Body>.

  • WriteStartEnvelope grava o elemento de envelope inicial, por exemplo, <soap:Envelope>.

Para gravar as marcas de elemento final correspondentes, chame WriteEndElement no gravador XML correspondente. Esses métodos raramente são chamados diretamente.

Lendo mensagens

A principal maneira de ler um corpo de mensagem é chamar GetReaderAtBodyContents. Você recebe de volta um XmlDictionaryReader que pode ser usado para ler o corpo da mensagem. Observe que as Message transições para o estado de leitura assim que GetReaderAtBodyContents são chamadas e não quando você usa o leitor XML retornado.

O método GetBody também permite que você acesse o corpo da mensagem como um objeto tipado. Internamente, esse método usa GetReaderAtBodyContents e, portanto, também faz a transição do estado da mensagem para o estado Read (consulte a propriedade State).

É uma boa prática verificar a propriedade IsEmpty, caso em que o corpo da mensagem está vazio e GetReaderAtBodyContents lança um InvalidOperationException. Além disso, se for uma mensagem recebida (por exemplo, a resposta), talvez você também queira verificar IsFault, o que indica se a mensagem contém uma falha.

A sobrecarga mais básica de GetBody desserializa o corpo da mensagem em uma instância de um tipo (indicado pelo parâmetro genérico) usando uma configuração DataContractSerializer com as configurações padrão e com a cota MaxItemsInObjectGraph desabilitada. Se você quiser usar um mecanismo de serialização diferente ou configurar DataContractSerializer de maneira não padrão, use a sobrecarga GetBody que usa um XmlObjectSerializer.

Por exemplo, o código a seguir extrai dados de um corpo de mensagem que contém um objeto serializado Person e imprime o nome da pessoa.

    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

Copiando uma mensagem em um buffer

Às vezes, é necessário acessar o corpo da mensagem mais de uma vez, por exemplo, para encaminhar a mesma mensagem para vários destinos como parte de um sistema editor-assinante. Nesse caso, é necessário armazenar em buffer toda a mensagem (incluindo o corpo) na memória. Podemos fazer isso chamando CreateBufferedCopy(Int32). Esse método usa um parâmetro inteiro que representa o tamanho máximo do buffer e cria um buffer não maior que esse tamanho. É importante definir isso como um valor seguro se a mensagem for proveniente de uma fonte não confiável.

O buffer é retornado como uma instância MessageBuffer. Você pode acessar dados no buffer de várias maneiras. A principal maneira é chamar CreateMessage para criar instâncias Message do buffer.

Outra maneira de acessar os dados no buffer é implementar a interface IXPathNavigable que a classe MessageBuffer implementa para acessar o XML subjacente diretamente. Algumas sobrecargas CreateNavigator permitem criar navegadores System.Xml.XPath protegidos por uma cota de nó, limitando o número de nós XML que podem ser visitados. Isso ajuda a evitar ataques de negação de serviço com base no longo tempo de processamento. Essa regra está desabilitada por padrão. Algumas sobrecargas CreateNavigator permitem que você especifique como o espaço em branco deve ser tratado no XML usando a enumeração XmlSpace, sendo o padrão XmlSpace.None.

Uma maneira final de acessar o conteúdo de um buffer de mensagem é gravar seu conteúdo em um fluxo usando WriteMessage.

O exemplo a seguir demonstra o processo de trabalhar com um MessageBuffer: uma mensagem de entrada é encaminhada para vários destinatários e, em seguida, registrada em log em um arquivo. Sem buffer, isso não é possível, pois o corpo da mensagem pode ser acessado apenas uma vez.

[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

A classe MessageBuffer tem outros membros que valem a pena observar. O método Close pode ser chamado para liberar recursos quando o conteúdo do buffer não for mais necessário. A propriedade BufferSize retorna o tamanho do buffer alocado. A propriedade MessageContentType retorna o tipo de conteúdo MIME da mensagem.

Acessando o corpo da mensagem para depuração

Para fins de depuração, você pode chamar o método ToString para obter uma representação da mensagem como uma cadeia de caracteres. Essa representação geralmente corresponde à maneira como uma mensagem seria exibida no fio, se fosse codificada com o codificador de texto, exceto pelo XML, que seria melhor formatado para legibilidade humana. A única exceção a isso é o corpo da mensagem. O corpo pode ser lido apenas uma vez e ToString não altera o estado da mensagem. Portanto, o método ToString pode não ser capaz de acessar o corpo e pode substituir um espaço reservado (por exemplo, "..." ou três pontos) em vez do corpo da mensagem. Portanto, não use ToString para registrar mensagens de log se o conteúdo do corpo das mensagens for importante.

Acessando outras partes da mensagem

Várias propriedades são fornecidas para acessar informações sobre a mensagem que não sejam seu conteúdo corporal. No entanto, elas não poderão ser chamadas depois que a mensagem for fechada:

  • A propriedade Headers representa os cabeçalhos da mensagem. Consulte a seção "Trabalhando com cabeçalhos" mais adiante neste tópico.

  • A propriedade Properties representa as propriedades da mensagem, que são partes de dados nomeados anexados à mensagem que geralmente não são emitidas quando a mensagem é enviada. Consulte a seção "Trabalhando com propriedades" mais adiante neste tópico.

  • A propriedade Version indicará a versão SOAP e WS-Addressing associada à mensagem ou None se SOAP estiver desabilitado.

  • A propriedade IsFault retornará true se a mensagem for de falha SOAP.

  • A propriedade IsEmpty retornará true se a mensagem estiver vazia.

Você pode usar o método GetBodyAttribute(String, String) para acessar um atributo específico no elemento wrapper do corpo (por exemplo, <soap:Body>) identificado por um nome e namespace específicos. Se o atributo não for encontrado, null será retornado. Esse método só pode ser chamado quando Message estiver no estado Criado (quando o corpo da mensagem ainda não tiver sido acessado).

Trabalhando com cabeçalhos

Uma Message pode conter qualquer número de fragmentos XML nomeados, chamados cabeçalhos. Cada fragmento normalmente é mapeado para um cabeçalho SOAP. Os cabeçalhos são acessados por meio da propriedade Headers do tipo MessageHeaders. MessageHeaders é uma coleção de objetos MessageHeaderInfo, e cabeçalhos individuais podem ser acessados por meio de sua interface IEnumerable ou por meio de seu indexador. Por exemplo, o código a seguir lista os nomes de todos os cabeçalhos em um 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

Adicionando, removendo, localizando cabeçalhos

Você pode adicionar um novo cabeçalho no final de todos os cabeçalhos existentes usando o método Add. Você pode usar o método Insert para inserir um cabeçalho em um índice específico. Os cabeçalhos existentes são deslocados para o item inserido. Os cabeçalhos são ordenados de acordo com o índice, e o primeiro índice disponível é 0. Você pode usar as várias sobrecargas de método CopyHeadersFrom para adicionar cabeçalhos de uma instância Message ou MessageHeaders diferente. Algumas sobrecargas copiam um cabeçalho individual, enquanto outras copiam todos. O método Clear remove todos os cabeçalhos. O método RemoveAt remove um cabeçalho em um índice específico (deslocando todos os cabeçalhos após ele). O método RemoveAll remove todos os cabeçalhos com um nome e um namespace específicos.

Recupere um cabeçalho específico usando o método FindHeader. Esse método usa o nome e o namespace do cabeçalho para localizar e retorna seu índice. Se o cabeçalho ocorrer mais de uma vez, uma exceção será lançada. Se o cabeçalho não for encontrado, ele retornará -1.

No modelo de cabeçalho SOAP, os cabeçalhos podem ter um valor Actor que especifica o destinatário pretendido do cabeçalho. A sobrecarga mais básica FindHeader pesquisa apenas cabeçalhos destinados ao receptor final da mensagem. No entanto, outra sobrecarga permite que você especifique quais valores Actor estão incluídos na pesquisa. Para obter mais informações, consulte as especificações do SOAP.

Um método CopyTo(MessageHeaderInfo[], Int32) é fornecido para copiar cabeçalhos de uma coleção MessageHeaders para uma matriz de objetos MessageHeaderInfo.

Para acessar os dados XML em um cabeçalho, você pode chamar GetReaderAtHeader e retornar um leitor XML para o índice de cabeçalho específico. Se você quiser desserializar o conteúdo do cabeçalho em um objeto, use GetHeader<T>(Int32) ou uma das outras sobrecargas. As sobrecargas mais básicas desserializam cabeçalhos usando DataContractSerializer configurada da maneira padrão. Se você quiser usar um serializador diferente ou uma configuração diferente do DataContractSerializer, use uma das sobrecargas que levam XmlObjectSerializer. Também há sobrecargas que levam o nome do cabeçalho, o namespace e, opcionalmente, uma lista de valores Actor em vez de um índice; essa é uma combinação de FindHeader e GetHeader.

Trabalhando com propriedades

Uma instância Message pode conter um número arbitrário de objetos nomeados de tipos arbitrários. Essa coleção é acessada por meio da propriedade Properties do tipo MessageProperties. A coleção implementa a interface IDictionary<TKey,TValue> e atua como um mapeamento de String para Object. Normalmente, os valores de propriedade não são mapeados diretamente para qualquer parte da mensagem no fio, mas fornecem várias dicas de processamento de mensagem para os vários canais na pilha de canais do WCF ou para a estrutura de serviço CopyTo(MessageHeaderInfo[], Int32). Para obter um exemplo, consulte Visão geral da arquitetura de transferência de dados.

Herdando da Classe de Mensagem

Se os tipos de mensagem internos criados usando CreateMessage não atenderem aos seus requisitos, crie uma classe derivada da classe Message.

Definindo o conteúdo do corpo da mensagem

Existem três técnicas principais para acessar dados em um corpo da mensagem: escrever, ler e copiá-los em um buffer. Essas operações, em última análise, resultam nos métodos OnWriteBodyContents, OnGetReaderAtBodyContentse OnCreateBufferedCopy sendo chamados, respectivamente, em sua classe derivada de Message. A classe base Message garante que apenas um desses métodos seja chamado para cada instância Message e que ele não seja chamado mais de uma vez. A classe base também garante que os métodos não sejam chamados em uma mensagem fechada. Não é necessário controlar o estado da mensagem em sua implementação.

OnWriteBodyContents é um método abstrato e deve ser implementado. A maneira mais básica de definir o conteúdo do corpo da mensagem é gravar usando esse método. Por exemplo, a mensagem a seguir contém 100.000 números aleatórios de 1 a 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.

Os métodos OnGetReaderAtBodyContents() e OnCreateBufferedCopy têm implementações padrão que funcionam para a maioria dos casos. As implementações padrão chamam OnWriteBodyContents, armazenam em buffer os resultados e funcionam com o buffer resultante. No entanto, em alguns casos, isso pode não ser suficiente. No exemplo anterior, a leitura da mensagem resulta em 100.000 elementos XML sendo armazenados em buffer, o que pode não ser desejável. Talvez você queira substituir OnGetReaderAtBodyContents() para retornar uma classe derivada personalizada XmlDictionaryReader que atende a números aleatórios. Em seguida, você pode substituir OnWriteBodyContents para usar o leitor que o método OnGetReaderAtBodyContents() retorna, conforme mostrado no exemplo a seguir.

    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

Da mesma forma, talvez você queira substituir OnCreateBufferedCopy para retornar sua própria MessageBuffer classe derivada.

Além de fornecer conteúdo do corpo da mensagem, sua classe derivada de mensagem também deve substituir as propriedades Version, Headers e Properties.

Observe que, se você criar uma cópia de uma mensagem, a cópia usará os cabeçalhos da mensagem do original.

Outros membros que podem ser substituídos

Você pode substituir os métodos OnWriteStartEnvelope, OnWriteStartHeaderse OnWriteStartBody para especificar como o envelope SOAP, cabeçalhos SOAP e marcas de início do elemento de corpo SOAP são gravados. Normalmente, correspondem a <soap:Envelope>, <soap:Header>e <soap:Body>. Normalmente, esses métodos não devem gravar nada se a propriedade Version retornar None.

Observação

A implementação padrão de OnGetReaderAtBodyContents chama OnWriteStartEnvelope e OnWriteStartBody, antes de chamar OnWriteBodyContents e armazenar os resultados em buffer. Os cabeçalhos não são removidos.

Substitua o método OnWriteMessage para alterar a forma como toda a mensagem é construída a partir de suas várias partes. O método OnWriteMessage é chamado de WriteMessage e na implementação padrão OnCreateBufferedCopy. Observe que substituir WriteMessage não é uma melhor prática. É melhor substituir os métodos apropriados On (por exemplo, OnWriteStartEnvelope, OnWriteStartHeaders e OnWriteBodyContents.)

Substitua OnBodyToString para substituir como o corpo da mensagem é representado durante a depuração. O padrão é representá-lo como três pontos ("..."). Observe que esse método poderá ser chamado várias vezes, quando o estado da mensagem não for fechado. Uma implementação desse método nunca deve causar nenhuma ação que deve ser executada apenas uma vez (como a leitura de um fluxo somente encaminhamento).

Substitua o método OnGetBodyAttribute para permitir o acesso a atributos no elemento de corpo SOAP. Esse método pode ser chamado várias vezes, mas o tipo base Message garante que ele só seja chamado quando a mensagem estiver no estado Criado. Não é necessário verificar o estado em uma implementação. A implementação padrão sempre retorna null, o que indica que não há atributos no elemento corpo.

Se o objeto Message precisar fazer qualquer limpeza especial quando o corpo da mensagem não for mais necessário, você poderá substituir OnClose. A implementação padrão não tem ação.

As propriedades IsEmpty e IsFault podem ser substituídas. Por padrão, ambas retornam false.