Visão geral da arquitetura de transferência de dados
O Windows Communication Foundation (WCF) pode ser considerado como uma infraestrutura de mensagens. Ele pode receber mensagens, processá-las e enviá-las para o código do usuário para ações adicionais, ou pode construir mensagens a partir de dados fornecidos pelo código do usuário e entregá-las a um destino. Este tópico, destinado a desenvolvedores avançados, descreve a arquitetura para lidar com mensagens e os dados contidos. Para obter uma visão mais simples e orientada a tarefas de como enviar e receber dados, consulte Especificando a transferência de dados em contratos de serviço.
Nota
Este tópico discute detalhes de implementação do WCF que não são visíveis examinando o modelo de objeto WCF. Duas palavras de cautela são necessárias no que diz respeito aos detalhes documentados da implementação. Em primeiro lugar, as descrições são simplificadas; A implementação real pode ser mais complexa devido a otimizações ou outros motivos. Em segundo lugar, você nunca deve confiar em detalhes de implementação específicos, mesmo documentados, porque eles podem mudar sem aviso prévio de versão para versão ou até mesmo em uma versão de manutenção.
Arquitetura Básica
No núcleo dos recursos de manipulação de mensagens do WCF está a Message classe, que é descrita em detalhes em Usando a classe de mensagem. Os componentes de tempo de execução do WCF podem ser divididos em duas partes principais: a pilha de canais e a estrutura de serviço, com a Message classe sendo o ponto de conexão.
A pilha de canais é responsável pela conversão entre uma instância válida Message e alguma ação que corresponde ao envio ou recebimento de dados de mensagens. No lado do envio, a pilha de canais toma uma instância válida Message e, após algum processamento, executa alguma ação que corresponde logicamente ao envio da mensagem. A ação pode ser enviar pacotes TCP ou HTTP, enfileirar a mensagem no serviço de enfileiramento de mensagens, gravar a mensagem em um banco de dados, salvá-la em um compartilhamento de arquivos ou qualquer outra ação, dependendo da implementação. A ação mais comum é enviar a mensagem através de um protocolo de rede. No lado da receção, acontece o oposto — uma ação é detetada (que pode ser pacotes TCP ou HTTP chegando ou qualquer outra ação) e, após o processamento, a pilha de canais converte essa ação em uma instância válida Message .
Você pode usar o WCF usando a Message classe e a pilha de canais diretamente. No entanto, fazê-lo é difícil e demorado. Além disso, o Message objeto não fornece suporte a metadados, portanto, você não pode gerar clientes WCF fortemente tipados se você usar WCF dessa maneira.
Portanto, o WCF inclui uma estrutura de serviço que fornece um modelo de programação fácil de usar que você pode usar para construir e receber Message
objetos. A estrutura de serviço mapeia serviços para tipos do .NET Framework por meio da noção de contratos de serviço e envia mensagens para operações de usuário que são simplesmente métodos do .NET Framework marcados com o OperationContractAttribute atributo (para obter mais detalhes, consulte Designing Service Contracts). Esses métodos podem ter parâmetros e valores de retorno. No lado do serviço, a estrutura de serviço converte instâncias de entrada Message em parâmetros e converte valores de retorno em instâncias de saída Message . Do lado do cliente, faz o contrário. Por exemplo, considere a FindAirfare
operação abaixo.
[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
Suponha que FindAirfare
é chamado no cliente. A estrutura de serviço no cliente converte os FromCity
parâmetros e ToCity
em uma instância de saída Message e passa para a pilha de canais a ser enviada.
No lado do serviço, quando uma Message instância chega da pilha de canais, a estrutura de serviço extrai os dados relevantes da mensagem para preencher os FromCity
parâmetros e ToCity
e, em seguida, chama o método do lado FindAirfare
do serviço. Quando o método retorna, a estrutura de serviço usa o valor inteiro retornado e o IsDirectFlight
parâmetro output e cria uma instância de Message objeto que contém essas informações. Em seguida, ele passa a Message
instância para a pilha de canais para ser enviada de volta ao cliente.
No lado do cliente, uma Message instância que contém a mensagem de resposta emerge da pilha de canais. A estrutura de serviço extrai o valor de retorno e o IsDirectFlight
valor e os retorna ao chamador do cliente.
Classe de mensagem
A Message classe destina-se a ser uma representação abstrata de uma mensagem, mas seu design está fortemente ligado à mensagem SOAP. A Message contém três informações principais: um corpo de mensagem, cabeçalhos de mensagem e propriedades de mensagem.
Corpo da Mensagem
O corpo da mensagem destina-se a representar a carga útil de dados real da mensagem. O corpo da mensagem é sempre representado como um Infoset XML. Isso não significa que todas as mensagens criadas ou recebidas no WCF devem estar no formato XML. Cabe à pilha de canais decidir como interpretar o corpo da mensagem. Ele pode emiti-lo como XML, convertê-lo para algum outro formato ou até mesmo omiti-lo completamente. É claro que, com a maioria das associações fornecidas pelo WCF, o corpo da mensagem é representado como conteúdo XML na seção body de um envelope SOAP.
É importante perceber que a Message
classe não contém necessariamente um buffer com dados XML representando o corpo. Logicamente, Message
contém um Infoset XML, mas este Infoset pode ser construído dinamicamente e pode nunca existir fisicamente na memória.
Colocando dados no corpo da mensagem
Não existe um mecanismo uniforme para colocar dados num corpo de mensagem. A Message classe tem um método abstrato, OnWriteBodyContents(XmlDictionaryWriter)que leva um XmlDictionaryWriter. Cada subclasse da Message classe é responsável por substituir esse método e escrever seu próprio conteúdo. O corpo da mensagem contém logicamente o Infoset XML que OnWriteBodyContent
produz. Por exemplo, considere a seguinte Message
subclasse.
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
Fisicamente, uma AirfareRequestMessage
ocorrência contém apenas duas cadeias de caracteres ("fromCity" e "toCity"). No entanto, logicamente, a mensagem contém o seguinte infoset XML:
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
É claro que você normalmente não criaria mensagens dessa maneira, porque você pode usar a estrutura de serviço para criar uma mensagem como a anterior a partir de parâmetros de contrato de operação. Além disso, a Message classe tem métodos estáticos CreateMessage
que você pode usar para criar mensagens com tipos comuns de conteúdo: uma mensagem vazia, uma mensagem que contém um objeto serializado para XML com o DataContractSerializer, uma mensagem que contém uma falha SOAP, uma mensagem que contém XML representado por um XmlReadere assim por diante.
Obtendo dados de um corpo de mensagem
Você pode extrair os dados armazenados em um corpo de mensagem de duas maneiras principais:
Você pode obter todo o corpo da mensagem de uma só vez chamando o WriteBodyContents(XmlDictionaryWriter) método e passando um gravador XML. O corpo completo da mensagem está escrito para este escritor. Obter todo o corpo da mensagem de uma só vez também é chamado de escrever uma mensagem. A escrita é feita principalmente pela pilha de canais ao enviar mensagens — alguma parte da pilha de canais geralmente terá acesso a todo o corpo da mensagem, codificá-la e enviá-la.
Outra maneira de obter informações do corpo da mensagem é ligar GetReaderAtBodyContents() e obter um leitor XML. O corpo da mensagem pode então ser acessado sequencialmente, conforme necessário, chamando métodos no leitor. Obter o corpo da mensagem peça por peça também é chamado de leitura de uma mensagem. A leitura da mensagem é usada principalmente pela estrutura de serviço ao receber mensagens. Por exemplo, quando o DataContractSerializer estiver em uso, a estrutura de serviço obterá um leitor XML sobre o corpo e o passará para o mecanismo de desserialização, que começará a ler a mensagem elemento por elemento e a construir o gráfico de objeto correspondente.
Um corpo de mensagem pode ser recuperado apenas uma vez. Isso torna possível trabalhar com fluxos somente para frente. Por exemplo, você pode escrever uma OnWriteBodyContents(XmlDictionaryWriter) substituição que lê de um FileStream e retorna os resultados como um Infoset XML. Você nunca precisará "retroceder" para o início do arquivo.
Os WriteBodyContents
métodos e GetReaderAtBodyContents
simplesmente verificam se o corpo da mensagem nunca foi recuperado antes e, em seguida, chamam OnWriteBodyContents
ou OnGetReaderAtBodyContents
, respectivamente.
Uso de mensagens no WCF
A maioria das mensagens pode ser classificada como de saída (aquelas que são criadas pela estrutura de serviço para serem enviadas pela pilha de canais) ou de entrada (aquelas que chegam da pilha de canais e são interpretadas pela estrutura de serviço). Além disso, a pilha de canais pode operar em buffer ou modo de streaming. A estrutura de serviço também pode expor um modelo de programação transmitido ou não transmitido. Isso leva aos casos listados na tabela a seguir, juntamente com detalhes simplificados de sua implementação.
Tipo de mensagem | Dados do corpo na mensagem | Implementação de gravação (OnWriteBodyContents) | Implementação Read (OnGetReaderAtBodyContents) |
---|---|---|---|
Outgoing, criado a partir de um modelo de programação não transmitido | Os dados necessários para gravar a mensagem (por exemplo, um objeto e a DataContractSerializer instância necessária para serializá-lo)* | Lógica personalizada para escrever a mensagem com base nos dados armazenados (por exemplo, chamar WriteObject o DataContractSerializer se esse é o serializador em uso)* |
Chamar OnWriteBodyContents , armazenar os resultados em buffer, retornar um leitor XML sobre o buffer |
Outgoing, criado a partir do modelo de programação em streaming | O Stream com os dados a serem escritos* |
Escreva dados do fluxo armazenado usando o IStreamProvider mecanismo* | Chamar OnWriteBodyContents , armazenar os resultados em buffer, retornar um leitor XML sobre o buffer |
Entrada da pilha de canais de streaming | Um Stream objeto que representa os dados que entram pela rede com um XmlReader sobre ele |
Escreva o conteúdo do armazenado XmlReader usando WriteNode |
Devolve o XmlReader |
Entrada da pilha de canais que não são de streaming | Um buffer que contém dados do corpo com um XmlReader sobre ele |
Grava o conteúdo do armazenado XmlReader usando WriteNode |
Devolve o lang armazenado |
* Estes itens não são implementados diretamente em Message
subclasses, mas em subclasses da BodyWriter classe. Para obter mais informações sobre o BodyWriter, consulte Usando a classe de mensagem.
Cabeçalhos de mensagens
Uma mensagem pode conter cabeçalhos. Um cabeçalho consiste logicamente em um Infoset XML que está associado a um nome, um namespace e algumas outras propriedades. Os cabeçalhos das mensagens são acessados usando a Headers
propriedade em Message. Cada cabeçalho é representado por uma MessageHeader classe. Normalmente, os cabeçalhos de mensagem são mapeados para cabeçalhos de mensagem SOAP ao usar uma pilha de canais configurada para trabalhar com mensagens SOAP.
Colocar informações em um cabeçalho de mensagem e extrair informações dele é semelhante a usar o corpo da mensagem. O processo é um pouco simplificado porque o streaming não é suportado. É possível acessar o conteúdo do mesmo cabeçalho mais de uma vez, e os cabeçalhos podem ser acessados em ordem arbitrária, forçando os cabeçalhos a serem sempre armazenados em buffer. Não há nenhum mecanismo de uso geral disponível para obter um leitor XML sobre um cabeçalho, mas há uma MessageHeader
subclasse interna ao WCF que representa um cabeçalho legível com tal capacidade. Esse tipo de é criado pela pilha de MessageHeader
canais quando uma mensagem com cabeçalhos de aplicativo personalizados chega. Isso permite que a estrutura de serviço use um mecanismo de desserialização, como o DataContractSerializer, para interpretar esses cabeçalhos.
Para obter mais informações, consulte Usando a classe de mensagem.
Propriedades da mensagem
Uma mensagem pode conter propriedades. Uma propriedade é qualquer objeto do .NET Framework associado a um nome de cadeia de caracteres. As propriedades são acessadas através da Properties
propriedade em Message
.
Ao contrário do corpo da mensagem e dos cabeçalhos da mensagem (que normalmente são mapeados para o corpo SOAP e cabeçalhos SOAP, respectivamente), as propriedades da mensagem normalmente não são enviadas ou recebidas junto com as mensagens. As propriedades da mensagem existem principalmente como um mecanismo de comunicação para passar dados sobre a mensagem entre os vários canais na pilha de canais e entre a pilha de canais e o modelo de serviço.
Por exemplo, o canal de transporte HTTP incluído como parte do WCF é capaz de produzir vários códigos de status HTTP, como "404 (Não encontrado)" e "500 (Erro interno do servidor)", quando envia respostas aos clientes. Antes de enviar uma mensagem de resposta, ele verifica se o Properties
Message
do contém uma propriedade chamada "httpResponse" que contém um objeto do tipo HttpResponseMessageProperty. Se tal propriedade for encontrada, ele examinará a StatusCode propriedade e usará esse código de status. Se não for encontrado, o código padrão "200 (OK)" é usado.
Para obter mais informações, consulte Usando a classe de mensagem.
A mensagem como um todo
Até agora, discutimos métodos para acessar as várias partes da mensagem isoladamente. No entanto, a Message classe também fornece métodos para trabalhar com a mensagem inteira como um todo. Por exemplo, o WriteMessage
método grava a mensagem inteira para um gravador XML.
Para que isso seja possível, um mapeamento deve ser definido entre a instância inteira Message
e um Infoset XML. Esse mapeamento, de fato, existe: o WCF usa o padrão SOAP para definir esse mapeamento. Quando uma Message
instância é gravada como um Infoset XML, o Infoset resultante é o envelope SOAP válido que contém a mensagem. Assim, WriteMessage
normalmente executaria as seguintes etapas:
Escreva a tag de abertura do elemento de envelope SOAP.
Escreva a tag de abertura do elemento de cabeçalho SOAP, escreva todos os cabeçalhos e feche o elemento de cabeçalho.
Escreva a tag de abertura do elemento body SOAP.
Chamada
WriteBodyContents
ou um método equivalente para escrever o corpo.Feche o corpo e os elementos do envelope.
As etapas anteriores estão intimamente ligadas ao padrão SOAP. Isso é complicado pelo fato de que existem várias versões do SOAP, por exemplo, é impossível escrever o elemento envelope SOAP corretamente sem conhecer a versão SOAP em uso. Além disso, em alguns casos, pode ser desejável desativar completamente esse mapeamento complexo específico do SOAP.
Para estes efeitos, uma Version
propriedade é fornecida em Message
. Ele pode ser definido para a versão SOAP a ser usada ao escrever a mensagem ou pode ser definido para None
evitar mapeamentos específicos do SOAP. Se a Version
propriedade estiver definida como None
, os métodos que funcionam com a mensagem inteira agem como se a mensagem consistisse apenas em seu corpo, por exemplo, WriteMessage
simplesmente chamariam WriteBodyContents
em vez de executar as várias etapas listadas acima. Espera-se que, nas mensagens recebidas, Version
sejam detetados automaticamente e configurados corretamente.
A pilha de canais
Canais
Como dito anteriormente, a pilha de canais é responsável por converter instâncias de saída Message em alguma ação (como enviar pacotes pela rede) ou converter alguma ação (como receber pacotes de rede) em instâncias de entrada Message
.
A pilha de canais é composta por um ou mais canais ordenados em uma sequência. Uma instância de Message
saída é passada para o primeiro canal na pilha (também chamado de canal mais alto), que o passa para o próximo canal para baixo na pilha, e assim por diante. A mensagem termina no último canal, que é chamado de canal de transporte. As mensagens recebidas são originadas no canal de transporte e são passadas de canal para canal até a pilha. A partir do canal mais alto, a mensagem geralmente é passada para a estrutura de serviço. Embora este seja o padrão habitual para mensagens de aplicativos, alguns canais podem funcionar de forma ligeiramente diferente, por exemplo, eles podem enviar suas próprias mensagens de infraestrutura sem receber uma mensagem de um canal acima.
Os canais podem operar na mensagem de várias maneiras à medida que ela passa pela pilha. A operação mais comum é adicionar um cabeçalho a uma mensagem de saída e ler cabeçalhos em uma mensagem de entrada. Por exemplo, um canal pode calcular a assinatura digital de uma mensagem e adicioná-la como um cabeçalho. Um canal também pode inspecionar esse cabeçalho de assinatura digital em mensagens recebidas e impedir que mensagens que não tenham uma assinatura válida subam na pilha de canais. Os canais também costumam definir ou inspecionar as propriedades da mensagem. O corpo da mensagem geralmente não é modificado, embora isso seja permitido, por exemplo, o canal de segurança WCF pode criptografar o corpo da mensagem.
Canais de transporte e codificadores de mensagens
O canal mais baixo na pilha é responsável por realmente transformar uma saída Message, como modificado por outros canais, em alguma ação. No lado da receção, este é o canal que converte alguma ação em um Message
processo de outros canais.
Como dito anteriormente, as ações podem ser variadas: enviar ou receber pacotes de rede através de vários protocolos, ler ou escrever a mensagem em um banco de dados ou enfileirar ou enfileirar a mensagem em uma fila de Enfileiramento de Mensagens, para fornecer apenas alguns exemplos. Todas essas ações têm uma coisa em comum: elas exigem uma transformação entre a instância do WCFMessage
e um grupo real de bytes que podem ser enviados, recebidos, lidos, gravados, enfileirados ou retirados da fila. O processo de conversão de um Message
em um grupo de bytes é chamado de codificação, e o processo inverso de criação de um Message
a partir de um grupo de bytes é chamado de decodificação.
A maioria dos canais de transporte usa componentes chamados codificadores de mensagens para realizar o trabalho de codificação e decodificação. Um codificador de mensagem é uma subclasse da MessageEncoder classe. MessageEncoder
Inclui várias ReadMessage
sobrecargas de WriteMessage
método para converter entre Message
e grupos de bytes.
No lado de envio, um canal de transporte de buffer passa o Message
objeto que recebeu de um canal acima dele para WriteMessage
. Ele recebe de volta uma matriz de bytes, que ele usa para executar sua ação (como empacotar esses bytes como pacotes TCP válidos e enviá-los para o destino correto). Um canal de transporte de streaming primeiro cria um Stream
(por exemplo, sobre a conexão TCP de saída) e, em seguida, passa o Stream
e o Message
que ele precisa enviar para a sobrecarga apropriada WriteMessage
, que grava a mensagem.
No lado do recebimento, um canal de transporte de buffer extrai bytes de entrada (por exemplo, de pacotes TCP de entrada) em uma matriz e chama ReadMessage
para obter um Message
objeto que ele pode passar mais acima na pilha de canais. Um canal de transporte de streaming cria um Stream
objeto (por exemplo, um fluxo de rede através da conexão TCP de entrada) e passa isso para ReadMessage
recuperar um Message
objeto.
A separação entre os canais de transporte e o codificador de mensagens não é obrigatória; É possível escrever um canal de transporte que não use um codificador de mensagens. No entanto, a vantagem desta separação é a facilidade de composição. Contanto que um canal de transporte use apenas a base MessageEncoder, ele pode funcionar com qualquer WCF ou codificador de mensagens de terceiros. Da mesma forma, o mesmo codificador pode normalmente ser usado em qualquer canal de transporte.
Operação do codificador de mensagens
Para descrever a operação típica de um codificador, é útil considerar os quatro casos a seguir.
Operação | Comentário |
---|---|
Codificação, Buffered | No modo de buffer, o codificador normalmente cria um buffer de tamanho variável e, em seguida, cria um gravador XML sobre ele. Em seguida, ele chama WriteMessage(XmlWriter) a mensagem que está sendo codificada, que escreve os cabeçalhos e, em seguida, o corpo usando WriteBodyContents(XmlDictionaryWriter), como explicado na seção anterior sobre Message este tópico. O conteúdo do buffer (representado como uma matriz de bytes) é retornado para o canal de transporte usar. |
Codificação, Transmitido | No modo streamed, a operação é semelhante à acima, mas mais simples. Não há necessidade de um buffer. Um gravador XML é normalmente criado sobre o fluxo e WriteMessage(XmlWriter) é chamado Message para escrevê-lo para este gravador. |
Decodificação, Buffered | Ao decodificar no modo de buffer, uma subclasse especial Message que contém os dados armazenados em buffer é normalmente criada. Os cabeçalhos da mensagem são lidos e um leitor XML posicionado no corpo da mensagem é criado. Este é o leitor que será devolvido com GetReaderAtBodyContents(). |
Decodificação, Streaming | Ao decodificar no modo streamed, uma subclasse Message especial é normalmente criada. O fluxo é avançado apenas o suficiente para ler todos os cabeçalhos e posicioná-lo no corpo da mensagem. Um leitor XML é então criado sobre o fluxo. Este é o leitor que será devolvido com GetReaderAtBodyContents(). |
Os codificadores também podem executar outras funções. Por exemplo, os codificadores podem agrupar leitores e gravadores XML. É caro criar um novo leitor ou gravador XML sempre que um é necessário. Portanto, os codificadores normalmente mantêm um pool de leitores e um pool de gravadores de tamanho configurável. Nas descrições da operação do codificador descritas anteriormente, sempre que a frase "criar um leitor/gravador XML" é usada, normalmente significa "tirar um do pool ou criar um se não estiver disponível". O codificador (e as subclasses que ele cria durante a Message
decodificação) contém lógica para retornar leitores e gravadores aos pools quando eles não forem mais necessários (por exemplo, quando o Message
estiver fechado).
WCF fornece três codificadores de mensagem, embora seja possível criar tipos personalizados adicionais. Os tipos fornecidos são Text, Binary, and Message Transmission Optimization Mechanism (MTOM). Eles são descritos em detalhes em Escolhendo um codificador de mensagens.
A interface IStreamProvider
Ao escrever uma mensagem de saída que contém um corpo transmitido para um gravador XML, o Message usa uma sequência de chamadas semelhante à seguinte em sua OnWriteBodyContents(XmlDictionaryWriter) implementação:
Escreva todas as informações necessárias antes do fluxo (por exemplo, a marca XML de abertura).
Escreva o fluxo.
Escreva qualquer informação após o fluxo (por exemplo, a marca XML de fechamento).
Isso funciona bem com codificações que são semelhantes à codificação XML textual. No entanto, algumas codificações não colocam informações XML Infoset (por exemplo, tags para iniciar e terminar elementos XML) junto com os dados contidos nos elementos. Por exemplo, na codificação MTOM, a mensagem é dividida em várias partes. Uma parte contém o XML Infoset, que pode conter referências a outras partes para o conteúdo real do elemento. O Infoset XML é normalmente pequeno em comparação com o conteúdo transmitido, por isso faz sentido armazenar o Infoset em buffer, escrevê-lo e, em seguida, escrever o conteúdo de forma transmitida. Isso significa que, no momento em que a tag do elemento de fechamento é gravada, o fluxo ainda não deve ter sido gravado.
Para este efeito, a IStreamProvider interface é usada. A interface tem um GetStream() método que retorna o fluxo a ser gravado. A maneira correta de escrever um corpo de mensagem transmitida é OnWriteBodyContents(XmlDictionaryWriter) a seguinte:
Escreva todas as informações necessárias antes do fluxo (por exemplo, a marca XML de abertura).
Chame a
WriteValue
sobrecarga no XmlDictionaryWriter que leva um IStreamProvider, com umaIStreamProvider
implementação que retorna o fluxo a ser gravado.Escreva qualquer informação após o fluxo (por exemplo, a marca XML de fechamento).
Com essa abordagem, o gravador XML tem uma escolha de quando chamar GetStream() e gravar os dados transmitidos. Por exemplo, os gravadores XML textuais e binários irão chamá-lo imediatamente e escrever o conteúdo transmitido entre as tags de início e fim. O gravador MTOM pode decidir ligar GetStream() mais tarde, quando estiver pronto para escrever a parte apropriada da mensagem.
Representando dados na estrutura de serviço
Conforme declarado na seção "Arquitetura básica" deste tópico, a estrutura de serviço é a parte do WCF que, entre outras coisas, é responsável pela conversão entre um modelo de programação amigável para dados de mensagem e instâncias reais Message
. Normalmente, uma troca de mensagens é representada na estrutura de serviço como um método .NET Framework marcado com o OperationContractAttribute atributo. O método pode receber alguns parâmetros e retornar um valor de retorno ou excluir parâmetros (ou ambos). No lado do serviço, os parâmetros de entrada representam a mensagem de entrada e o valor de retorno e os parâmetros de saída representam a mensagem de saída. Do lado do cliente, o inverso é verdadeiro. O modelo de programação para descrever mensagens usando parâmetros e o valor de retorno é descrito em detalhes em Especificando transferência de dados em contratos de serviço. No entanto, esta seção fornecerá uma breve visão geral.
Modelos de Programação
A estrutura de serviço WCF suporta cinco modelos de programação diferentes para descrever mensagens:
1. A mensagem vazia
Este é o caso mais simples. Para descrever uma mensagem de entrada vazia, não use nenhum parâmetro de entrada.
[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer
Para descrever uma mensagem de saída vazia, use um valor de retorno vazio e não use nenhum parâmetro de saída:
[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)
Observe que isso é diferente de um contrato de operação unidirecional:
[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)
SetDesiredTemperature
No exemplo, um padrão de troca de mensagens bidirecional é descrito. Uma mensagem é retornada da operação, mas está vazia. É possível retornar uma falha da operação. No exemplo "Definir lâmpada", o padrão de troca de mensagens é unidirecional, portanto, não há nenhuma mensagem de saída para descrever. Neste caso, o serviço não pode comunicar qualquer estado ao cliente.
2. Usando a classe de mensagem diretamente
É possível usar a Message classe (ou uma de suas subclasses) diretamente em um contrato de operação. Neste caso, a estrutura de serviço apenas passa a Message
partir da operação para a pilha de canais e vice-versa, sem processamento adicional.
Existem dois casos de uso principais para usar Message
diretamente. Você pode usar isso para cenários avançados, quando nenhum dos outros modelos de programação oferece flexibilidade suficiente para descrever sua mensagem. Por exemplo, talvez você queira usar arquivos no disco para descrever uma mensagem, com as propriedades do arquivo se tornando cabeçalhos de mensagem e o conteúdo do arquivo se tornando o corpo da mensagem. Em seguida, você pode criar algo semelhante ao seguinte.
public class FileMessage : Message
// Code not shown.
Public Class FileMessage
Inherits Message
' Code not shown.
O segundo uso comum em Message
um contrato de operação é quando um serviço não se preocupa com o conteúdo específico da mensagem e age sobre a mensagem como em uma caixa preta. Por exemplo, você pode ter um serviço que encaminha mensagens para vários outros destinatários. O contrato pode ser escrito da seguinte forma.
[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
A linha Action="*" desativa efetivamente o envio de mensagens e garante que todas as mensagens enviadas para o IForwardingService
contrato cheguem à ForwardMessage
operação. (Normalmente, o dispatcher examinaria o cabeçalho "Action" da mensagem para determinar a qual operação ela se destina. Action="*" significa "todos os valores possíveis do cabeçalho Action".) A combinação de Action="*" e o uso de Message como parâmetro é conhecida como "contrato universal" porque é capaz de receber todas as mensagens possíveis. Para poder enviar todas as mensagens possíveis, use Message como o valor de retorno e defina ReplyAction
como "*". Isso impedirá que a estrutura de serviço adicione seu próprio cabeçalho Ação, permitindo que você controle esse cabeçalho usando o Message
objeto retornado.
3. Contratos de mensagem
O WCF fornece um modelo de programação declarativa para descrever mensagens, chamado contratos de mensagem. Este modelo é descrito em detalhes em Usando contratos de mensagem. Essencialmente, toda a mensagem é representada por um único tipo do .NET Framework que usa atributos como o MessageBodyMemberAttribute e MessageHeaderAttribute para descrever quais partes da classe de contrato de mensagem devem ser mapeadas para qual parte da mensagem.
Os contratos de mensagem fornecem muito controle sobre as instâncias resultantes Message
(embora, obviamente, não tanto controle quanto usar a Message
classe diretamente). Por exemplo, os corpos das mensagens geralmente são compostos por várias informações, cada uma representada por seu próprio elemento XML. Esses elementos podem ocorrer diretamente no corpo (modo nu ) ou podem ser encapsulados em um elemento XML abrangente. O uso do modelo de programação de contrato de mensagem permite que você tome a decisão bare-versus-wrapped e controle o nome do wrapper e o namespace.
O exemplo de código a seguir de um contrato de mensagem demonstra esses recursos.
[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
Os itens marcados para serem serializados (com o MessageBodyMemberAttribute, MessageHeaderAttributeou outros atributos relacionados) devem ser serializáveis para participar de um contrato de mensagem. Para obter mais informações, consulte a seção "Serialização" mais adiante neste tópico.
4. Parâmetros
Muitas vezes, um desenvolvedor que deseja descrever uma operação que atua em várias partes de dados não precisa do grau de controle que os contratos de mensagem fornecem. Por exemplo, ao criar novos serviços, geralmente não se deseja tomar a decisão bare-versus-wrapped e decidir sobre o nome do elemento wrapper. Tomar essas decisões geralmente requer profundo conhecimento de serviços Web e SOAP.
A estrutura de serviço WCF pode escolher automaticamente a melhor e mais interoperável representação SOAP para enviar ou receber várias informações relacionadas, sem forçar essas escolhas ao usuário. Isso é feito simplesmente descrevendo essas informações como parâmetros ou valores de retorno de um contrato de operação. Por exemplo, considere o seguinte contrato de operação.
[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
ByVal customerID As String, _
ByVal item As String, _
ByVal quantity As Integer)
A estrutura de serviço decide automaticamente colocar todas as três informações (customerID
, , e quantity
) no corpo da mensagem e envolvê-las em um elemento wrapper chamado SubmitOrderRequest
item
.
Descrever as informações a serem enviadas ou recebidas como uma lista simples de parâmetros do contrato de operação é a abordagem recomendada, a menos que existam razões especiais para mudar para o contrato de mensagem mais complexo ou Message
modelos de programação baseados em modelos.
5. Fluxo
O uso Stream
ou uma de suas subclasses em um contrato de operação ou como uma única parte do corpo da mensagem em um contrato de mensagem pode ser considerado um modelo de programação separado dos descritos acima. Usar Stream
desta forma é a única maneira de garantir que seu contrato será utilizável de forma transmitida, sem escrever sua própria subclasse compatível com Message
streaming. Para obter mais informações, consulte Dados grandes e streaming.
Quando Stream
ou uma de suas subclasses é usada dessa maneira, o serializador não é invocado. Para mensagens de saída, uma subclasse de streaming Message
especial é criada e o fluxo é escrito conforme descrito na seção na IStreamProvider interface. Para mensagens de entrada, a estrutura de serviço cria uma Stream
subclasse sobre a mensagem de entrada e a fornece à operação.
Restrições do modelo de programação
Os modelos de programação descritos acima não podem ser arbitrariamente combinados. Por exemplo, se uma operação aceita um tipo de contrato de mensagem, o contrato de mensagem deve ser seu único parâmetro de entrada. Além disso, a operação deve retornar uma mensagem vazia (tipo de retorno de vazio) ou outro contrato de mensagem. Essas restrições de modelo de programação são descritas nos tópicos para cada modelo de programação específico: Usando contratos de mensagem, Usando a classe de mensagem e Grandes dados e streaming.
Mensagem Formatters
Os modelos de programação descritos acima são suportados pela conexão de componentes chamados message formatters na estrutura de serviço. Message formatters são tipos que implementam a IClientMessageFormatter interface ou IDispatchMessageFormatter , ou ambas, para uso em clientes e clientes WCF de serviço, respectivamente.
As mensagens são normalmente conectadas por comportamentos. Por exemplo, os DataContractSerializerOperationBehavior plugs no formatador de mensagem de contrato de dados. Isso é feito no lado do serviço, definindo Formatter para o formatador correto no ApplyDispatchBehavior(OperationDescription, DispatchOperation) método, ou no lado do cliente, definindo Formatter para o formatador correto no ApplyClientBehavior(OperationDescription, ClientOperation) método.
As tabelas a seguir listam os métodos que um formatador de mensagem pode implementar.
Interface | Método | Ação |
---|---|---|
IDispatchMessageFormatter | DeserializeRequest(Message, Object[]) | Converte uma entrada Message em parâmetros de operação |
IDispatchMessageFormatter | SerializeReply(MessageVersion, Object[], Object) | Cria um valor de retorno de saída Message da operação / parâmetros de saída |
IClientMessageFormatter | SerializeRequest(MessageVersion, Object[]) | Cria uma saída Message dos parâmetros de operação |
IClientMessageFormatter | DeserializeReply(Message, Object[]) | Converte um valor de entrada Message em um valor de retorno/parâmetros de saída |
Serialização
Sempre que você usar contratos de mensagem ou parâmetros para descrever o conteúdo da mensagem, você deve usar a serialização para converter entre tipos do .NET Framework e representação XML Infoset. A serialização é usada em outros locais no WCF, por exemplo, Message tem um método Generic GetBody que você pode usar para ler todo o corpo da mensagem desserializada em um objeto.
O WCF suporta duas tecnologias de serialização "prontas para uso" para serializar e desserializar parâmetros e partes de mensagem: o DataContractSerializer e o XmlSerializer
. Além disso, você pode escrever serializadores personalizados. No entanto, outras partes do WCF (como o método Generic GetBody
ou a serialização de falhas SOAP) podem ser restritas para usar apenas as XmlObjectSerializer subclasses (DataContractSerializer e NetDataContractSerializer, mas não o XmlSerializer), ou podem até mesmo ser codificadas para usar apenas o DataContractSerializer.
O XmlSerializer
é o mecanismo de serialização usado em ASP.NET serviços Web. O DataContractSerializer
é o novo mecanismo de serialização que compreende o novo modelo de programação de contrato de dados. DataContractSerializer
é a escolha padrão, e a escolha de usar o XmlSerializer
pode ser feita por operação usando o DataContractFormatAttribute atributo.
DataContractSerializerOperationBehavior e XmlSerializerOperationBehavior são os comportamentos de operação responsáveis por conectar a mensagem formatters for the DataContractSerializer
e the XmlSerializer
, respectivamente. O DataContractSerializerOperationBehavior comportamento pode realmente operar com qualquer serializador derivado de XmlObjectSerializer, incluindo o NetDataContractSerializer (descrito em detalhes em Usando serialização autônoma). O comportamento chama uma das sobrecargas de CreateSerializer
método virtual para obter o serializador. Para conectar um serializador diferente, crie uma nova DataContractSerializerOperationBehavior subclasse e substitua ambas as CreateSerializer
sobrecargas.