Compartilhar via


Utilizando contratos de mensagem

Normalmente, ao criar aplicativos WCF (Windows Communication Foundation), os desenvolvedores prestam muita atenção às estruturas de dados e aos problemas de serialização e não precisam se preocupar com a estrutura das mensagens nas quais os dados são transportados. Para esses aplicativos, a criação de contratos de dados para os parâmetros ou valores retornados é simples. (Para obter mais informações, confira Como especificar a transferência de dados em contratos de serviço.)

No entanto, às vezes, o controle completo sobre a estrutura de uma mensagem SOAP é tão importante quanto o controle sobre o conteúdo dela. Isso é especialmente verdadeiro quando a interoperabilidade é importante ou para controlar especificamente problemas de segurança no nível da mensagem ou em parte da mensagem. Nesses casos, você pode criar um contrato de mensagem que permite especificar a estrutura precisa da mensagem SOAP necessária.

Este tópico discute como usar os vários atributos de contrato de mensagem para criar um contrato de mensagem específico para sua operação.

Como usar contratos de mensagem em operações

O WCF dá suporte a operações modeladas no estilo RPC (chamada de procedimento remoto) ou no estilo de mensagem. Em uma operação no estilo RPC, você pode usar qualquer tipo serializável e ter acesso aos recursos disponíveis para chamadas locais, como vários parâmetros e os parâmetros ref e out. Nesse estilo, a forma de serialização escolhida controla a estrutura dos dados nas mensagens subjacentes, e o runtime do WCF cria as mensagens para dar suporte à operação. Isso permite que os desenvolvedores que não estejam familiarizados com o SOAP e as mensagens SOAP criem e usem aplicativos de serviço de maneira rápida e fácil.

O exemplo de código a seguir mostra uma operação de serviço modelada no estilo RPC.

[OperationContract]  
public BankingTransactionResponse PostBankingTransaction(BankingTransaction bt);  

Normalmente, um contrato de dados é suficiente para definir o esquema das mensagens. Por exemplo, no exemplo anterior, é suficiente para a maioria dos aplicativos se BankingTransaction e BankingTransactionResponse têm contratos de dados para definir o conteúdo das mensagens SOAP subjacentes. Para obter mais informações sobre contratos de dados, confira Como usar contratos de dados.

No entanto, ocasionalmente, é necessário controlar com precisão como a estrutura da mensagem SOAP é transmitida pela rede. O cenário mais comum para esse caso é inserir cabeçalhos SOAP personalizados. Outro cenário comum é definir propriedades de segurança para os cabeçalhos e o corpo da mensagem, ou seja, decidir se esses elementos são assinados digitalmente e criptografados. Por fim, algumas pilhas SOAP de terceiros exigem que as mensagens estejam em um formato específico. As operações no estilo de mensagem fornecem esse controle.

Uma operação no estilo de mensagem tem, no máximo, um parâmetro e um valor retornado em que ambos os tipos são tipos de mensagens. Ou seja, eles são serializados diretamente em uma estrutura de mensagem SOAP especificada. Pode ser qualquer tipo marcado com o MessageContractAttribute ou o tipo Message. O exemplo de código a seguir mostra uma operação semelhante ao estilo RCP anterior, mas que usa o estilo de mensagem.

Por exemplo, se BankingTransaction e BankingTransactionResponse forem tipos que são contratos de mensagem, o código nas operações a seguir será válido.

[OperationContract]  
BankingTransactionResponse Process(BankingTransaction bt);  
[OperationContract]  
void Store(BankingTransaction bt);  
[OperationContract]  
BankingTransactionResponse GetResponse();  

No entanto, o código a seguir é inválido.

[OperationContract]  
bool Validate(BankingTransaction bt);  
// Invalid, the return type is not a message contract.  
[OperationContract]  
void Reconcile(BankingTransaction bt1, BankingTransaction bt2);  
// Invalid, there is more than one parameter.  

Uma exceção é gerada para qualquer operação que envolva um tipo de contrato de mensagem e que não siga um dos padrões válidos. É claro que as operações que não envolvem tipos de contrato de mensagem não estão sujeitas a essas restrições.

Se um tipo tiver um contrato de mensagem e um contrato de dados, somente o contrato de mensagem será considerado quando o tipo for usado em uma operação.

Como definir contratos de mensagem

Para definir um contrato de mensagem para um tipo (ou seja, para definir o mapeamento entre o tipo e um envelope SOAP), aplique o tipo MessageContractAttribute. Em seguida, aplique o MessageHeaderAttribute aos membros do tipo que deseja transformar em cabeçalhos SOAP e aplique o MessageBodyMemberAttribute aos membros que deseja transformar em partes do corpo SOAP da mensagem.

O código a seguir fornece um exemplo de como usar um contrato de mensagem.

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public Operation operation;  
  [MessageHeader] public DateTime transactionDate;  
  [MessageBodyMember] private Account sourceAccount;  
  [MessageBodyMember] private Account targetAccount;  
  [MessageBodyMember] public int amount;  
}  

Ao usar esse tipo como um parâmetro de operação, o seguinte envelope SOAP é gerado:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">  
  <s:Header>  
    <h:operation xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">Deposit</h:operation>  
    <h:transactionDate xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">2012-02-16T16:10:00</h:transactionDate>  
  </s:Header>  
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
    <BankingTransaction xmlns="http://tempuri.org/">  
      <amount>0</amount>  
      <sourceAccount xsi:nil="true"/>  
      <targetAccount xsi:nil="true"/>  
    </BankingTransaction>  
  </s:Body>  
</s:Envelope>  

Observe que operation e transactionDate são exibidos como cabeçalhos SOAP e o corpo SOAP consiste em um elemento wrapper BankingTransaction que contém sourceAccount, targetAccount e amount.

Você pode aplicar o MessageHeaderAttribute e o MessageBodyMemberAttribute a todos os campos, propriedades e eventos, independentemente de serem públicos, privados, protegidos ou internos.

O MessageContractAttribute permite que você especifique os atributos WrapperName e WrapperNamespace que controlam o nome do elemento wrapper no corpo da mensagem SOAP. Por padrão, o nome do tipo de contrato de mensagem é usado para o wrapper, e o namespace no qual o contrato de mensagem é definido http://tempuri.org/ é usado como o namespace padrão.

Observação

Os atributos KnownTypeAttribute são ignorados em contratos de mensagem. Se um KnownTypeAttribute for necessário, coloque-o na operação que esteja usando o contrato de mensagem em questão.

Como controlar namespaces e nomes de partes do corpo e do cabeçalho

Na representação SOAP de um contrato de mensagem, cada cabeçalho e parte do corpo são mapeados para um elemento XML que tem um nome e um namespace.

Por padrão, o namespace é o mesmo que o namespace do contrato de serviço no qual a mensagem está participando, e o nome é determinado pelo nome do membro ao qual os atributos MessageHeaderAttribute ou MessageBodyMemberAttribute são aplicados.

Você pode alterar esses padrões manipulando o MessageContractMemberAttribute.Name e o MessageContractMemberAttribute.Namespace (na classe pai dos atributos MessageHeaderAttribute e MessageBodyMemberAttribute).

Considere a classe no exemplo de código a seguir.

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public Operation operation;  
  [MessageHeader(Namespace="http://schemas.contoso.com/auditing/2005")] public bool IsAudited;  
  [MessageBodyMember(Name="transactionData")] public BankingTransactionData theData;  
}  

Neste exemplo, o cabeçalho IsAudited está no namespace especificado no código, e a parte do corpo que representa o membro theData é representada por um elemento XML com o nome transactionData. Veja a seguir o XML gerado para esse contrato de mensagem.

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">  
  <s:Header>  
    <h:IsAudited xmlns:h="http://schemas.contoso.com/auditing/2005" xmlns="http://schemas.contoso.com/auditing/2005">false</h:IsAudited>  
    <h:operation xmlns:h="http://tempuri.org/" xmlns="http://tempuri.org/">Deposit</h:operation>  
  </s:Header>  
  <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">  
    <AuditedBankingTransaction xmlns="http://tempuri.org/">  
      <transactionData/>  
    </AuditedBankingTransaction>  
  </s:Body>  
</s:Envelope>  

Como controlar se as partes do corpo SOAP estão encapsuladas

Por padrão, as partes do corpo SOAP são serializadas em um elemento encapsulado. Por exemplo, o código a seguir mostra o elemento wrapper HelloGreetingMessage gerado com base no nome do tipo MessageContractAttribute no contrato de mensagem da mensagem HelloGreetingMessage.

[MessageContract]
public class HelloGreetingMessage
{
  private string localGreeting;

  [MessageBodyMember(
    Name = "Salutations",
    Namespace = "http://www.examples.com"
  )]
  public string Greeting
  {
    get { return localGreeting; }
    set { localGreeting = value; }
  }
}

/*
 The following is the request message, edited for clarity.

  <s:Envelope>
    <s:Header>
      <!-- Note: Some header content has been removed for clarity.
      <a:Action>http://GreetingMessage/Action</a:Action>
      <a:To s:mustUnderstand="1"></a:To>
    </s:Header>
    <s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <HelloGreetingMessage xmlns="Microsoft.WCF.Documentation">
        <Salutations xmlns="http://www.examples.com">Hello.</Salutations>
      </HelloGreetingMessage>
    </s:Body>
 </s:Envelope>
 */
<MessageContract> _
Public Class HelloGreetingMessage
    Private localGreeting As String

    <MessageBodyMember(Name:="Salutations", Namespace:="http://www.examples.com")> _
    Public Property Greeting() As String
        Get
            Return localGreeting
        End Get
        Set(ByVal value As String)
            localGreeting = value
        End Set
    End Property
End Class

'  
'   The following is the request message, edited for clarity.
'    
'    <s:Envelope>
'      <s:Header>
'        <!-- Note: Some header content has been removed for clarity.
'        <a:Action>http://GreetingMessage/Action</a:Action> 
'        <a:To s:mustUnderstand="1"></a:To>
'      </s:Header>
'      <s:Body u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
'        <HelloGreetingMessage xmlns="Microsoft.WCF.Documentation">
'          <Salutations xmlns="http://www.examples.com">Hello.</Salutations>
'      </s:Body>
'   </s:Envelope>
'   

Para suprimir o elemento wrapper, defina a propriedade IsWrapped como false. Para controlar o nome e o namespace do elemento wrapper, use as propriedades WrapperName e WrapperNamespace.

Observação

Ter mais de uma parte do corpo da mensagem em mensagens que não estão encapsuladas não está em conformidade com o WS-I Basic Profile 1.1 e não é recomendado ao criar contratos de mensagem. No entanto, pode ser necessário ter mais de uma parte do corpo da mensagem desencapsulada em determinados cenários de interoperabilidade específicos. Se você pretende transmitir mais de um dado em um corpo de mensagem, recomendamos usar o modo padrão (encapsulado). Ter mais de um cabeçalho de mensagem em mensagens desencapsuladas é completamente aceitável.

Como usar tipos personalizados em contratos de mensagem

Cada cabeçalho de mensagem individual e parte do corpo da mensagem são serializados (transformado em XML) por meio do mecanismo de serialização escolhido para o contrato de serviço em que a mensagem é usada. O mecanismo de serialização padrão, o XmlFormatter, pode lidar com qualquer tipo que tenha um contrato de dados, seja explícita (tendo o System.Runtime.Serialization.DataContractAttribute) ou implicitamente (sendo um tipo primitivo, tendo o System.SerializableAttribute etc.). Para saber mais, confira Como usar contratos de dados.

No exemplo anterior, os tipos Operation e BankingTransactionData precisam ter um contrato de dados, e transactionDate é serializável porque DateTime é um primitivo (e, portanto, tem um contrato de dados implícito).

No entanto, é possível mudar para outro mecanismo de serialização, o XmlSerializer. Se você fizer essa mudança, garanta que todos os tipos usados para as partes do corpo e os cabeçalhos da mensagem sejam serializáveis usando o XmlSerializer.

Como usar matrizes em contratos de mensagem

Você pode usar matrizes de elementos repetidos em contratos de mensagens de duas maneiras.

A primeira é usar um MessageHeaderAttribute ou um MessageBodyMemberAttribute diretamente na matriz. Nesse caso, toda a matriz é serializada como um elemento (ou seja, um cabeçalho ou uma parte do corpo) com vários elementos filho. Considere a classe no exemplo a seguir.

[MessageContract]  
public class BankingDepositLog  
{  
  [MessageHeader] public int numRecords;  
  [MessageHeader] public DepositRecord[] records;  
  [MessageHeader] public int branchID;  
}  

Isso resulta em cabeçalhos SOAP semelhantes aos mostrados a seguir.

<BankingDepositLog>  
<numRecords>3</numRecords>  
<records>  
  <DepositRecord>Record1</DepositRecord>  
  <DepositRecord>Record2</DepositRecord>  
  <DepositRecord>Record3</DepositRecord>  
</records>  
<branchID>20643</branchID>  
</BankingDepositLog>  

Uma alternativa a isso é usar o MessageHeaderArrayAttribute. Nesse caso, cada elemento de matriz é serializado de maneira independente e, portanto, cada elemento da matriz tem um cabeçalho, parecido com o mostrado a seguir.

<numRecords>3</numRecords>  
<records>Record1</records>  
<records>Record2</records>  
<records>Record3</records>  
<branchID>20643</branchID>  

O nome padrão das entradas de matriz é o nome do membro ao qual os atributos MessageHeaderArrayAttribute são aplicados.

O atributo MessageHeaderArrayAttribute herda do MessageHeaderAttribute. Ele tem o mesmo conjunto de recursos dos atributos que não são de matriz. Por exemplo, é possível definir a ordem, o nome e o namespace para uma matriz de cabeçalhos da mesma forma que você o define para um só cabeçalho. Quando você usa a propriedade Order em uma matriz, ela se aplica a toda a matriz.

Você só pode aplicar o MessageHeaderArrayAttribute a matrizes, não a coleções.

Como usar matrizes de bytes em contratos de mensagem

As matrizes de bytes, quando usadas com os atributos que não são de matriz (MessageBodyMemberAttribute e MessageHeaderAttribute), não são tratadas como matrizes, mas como um tipo primitivo especial representado como dados codificados em Base64 no XML resultante.

Quando você usa matrizes de bytes com o atributo de matriz MessageHeaderArrayAttribute, os resultados dependem do serializador em uso. Com o serializador padrão, a matriz é representada como uma entrada individual para cada byte. No entanto, quando o XmlSerializer é selecionado, (usando o XmlSerializerFormatAttribute no contrato de serviço), as matrizes de bytes são tratadas como dados Base64, independentemente de os atributos de matriz ou que não são de matriz serem usados.

Como assinar e criptografar partes da mensagem

Um contrato de mensagem pode indicar se os cabeçalhos e/ou o corpo da mensagem devem ser assinados digitalmente e criptografados.

Isso é feito pela definição da propriedade MessageContractMemberAttribute.ProtectionLevel nos atributos MessageHeaderAttribute e MessageBodyMemberAttribute. A propriedade é uma enumeração do tipo System.Net.Security.ProtectionLevel e pode ser definida como None (sem criptografia ou assinatura), Sign (somente assinatura digital) ou EncryptAndSign (criptografia e assinatura digital). O padrão é EncryptAndSign.

Para que esses recursos de segurança funcionem, você precisa configurar corretamente a associação e os comportamentos. Se você usar esses recursos de segurança sem a configuração correta (por exemplo, tentar assinar uma mensagem sem fornecer suas credenciais), uma exceção será gerada no momento da validação.

Para cabeçalhos de mensagem, o nível de proteção é determinado individualmente para cada cabeçalho.

Para partes do corpo da mensagem, o nível de proteção pode ser considerado como o "nível mínimo de proteção". O corpo tem apenas um nível de proteção, independentemente do número de partes do corpo. O nível de proteção do corpo é determinado pela configuração de propriedade ProtectionLevel mais alta de todas as partes do corpo. No entanto, você deve definir o nível de proteção de cada parte do corpo para o nível de proteção mínimo real necessário.

Considere a classe no exemplo de código a seguir.

[MessageContract]  
public class PatientRecord  
{  
   [MessageHeader(ProtectionLevel=None)] public int recordID;  
   [MessageHeader(ProtectionLevel=Sign)] public string patientName;  
   [MessageHeader(ProtectionLevel=EncryptAndSign)] public string SSN;  
   [MessageBodyMember(ProtectionLevel=None)] public string comments;  
   [MessageBodyMember(ProtectionLevel=Sign)] public string diagnosis;  
   [MessageBodyMember(ProtectionLevel=EncryptAndSign)] public string medicalHistory;  
}  

Neste exemplo, o cabeçalho recordID não está protegido, patientName é signed, e SSN está criptografado e assinado. Pelo menos uma parte do corpo, medicalHistory, tem EncryptAndSign aplicado e, portanto, todo o corpo da mensagem é criptografado e assinado, embora as partes do corpo de comentários e diagnósticos especifiquem níveis mais baixos de proteção.

Ação SOAP

O SOAP e os padrões de serviços Web relacionados definem uma propriedade chamada Action que pode estar presente para cada mensagem SOAP enviada. As propriedades OperationContractAttribute.Action e OperationContractAttribute.ReplyAction da operação controlam o valor dessa propriedade.

Atributos do cabeçalho SOAP

O padrão SOAP define os seguintes atributos que podem existir em um cabeçalho:

  • Actor/Role (Actor em SOAP 1.1, Role em SOAP 1.2)

  • MustUnderstand

  • Relay

O atributo Actor ou Role especifica o URI (Uniform Resource Identifier) do nó ao qual determinado cabeçalho se destina. O atributo MustUnderstand especifica se o nó que processa o cabeçalho precisa entendê-lo. O atributo Relay especifica se o cabeçalho deve ser retransmitido para os nós downstream. O WCF não executa nenhum processamento desses atributos em mensagens de entrada, exceto o atributo MustUnderstand, conforme especificado na seção "Controle de versão do contrato de mensagem" mais adiante neste tópico. No entanto, ele permite que você leia e escreva esses atributos conforme necessário, conforme mostrado na descrição a seguir.

Ao enviar uma mensagem, esses atributos não são emitidos por padrão. Você pode alterar isso de duas maneiras. Primeiro, você pode definir estaticamente os atributos para os valores desejados alterando as propriedades MessageHeaderAttribute.Actor, MessageHeaderAttribute.MustUnderstand e MessageHeaderAttribute.Relay, conforme mostrado no exemplo de código a seguir. (Observe que não há nenhuma propriedade Role. A definição da propriedade Actor emitirá o atributo Role se você estiver usando o SOAP 1.2).

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader(Actor="http://auditingservice.contoso.com", MustUnderstand=true)] public bool IsAudited;  
  [MessageHeader] public Operation operation;  
  [MessageBodyMember] public BankingTransactionData theData;  
}  

A segunda maneira de controlar esses atributos é dinamicamente, por meio do código. Faça isso encapsulando o tipo de cabeçalho desejado no tipo MessageHeader<T> (não confunda esse tipo com a versão não genérica) e usando o tipo com o MessageHeaderAttribute. Em seguida, você pode usar propriedades no MessageHeader<T> para definir os atributos SOAP, conforme mostrado no exemplo de código a seguir.

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public MessageHeader<bool> IsAudited;  
  [MessageHeader] public Operation operation;  
  [MessageBodyMember] public BankingTransactionData theData;  
}  
// application code:  
BankingTransaction bt = new BankingTransaction();  
bt.IsAudited = new MessageHeader<bool>();  
bt.IsAudited.Content = false; // Set IsAudited header value to "false"  
bt.IsAudited.Actor="http://auditingservice.contoso.com";  
bt.IsAudited.MustUnderstand=true;  

Se você usar os mecanismos de controle dinâmico e estático, as configurações estáticas serão usadas como padrão, mas posteriormente poderão ser substituídas usando o mecanismo dinâmico, conforme mostrado no código a seguir.

[MessageHeader(MustUnderstand=true)] public MessageHeader<Person> documentApprover;  
// later on in the code:  
BankingTransaction bt = new BankingTransaction();  
bt.documentApprover = new MessageHeader<Person>();  
bt.documentApprover.MustUnderstand = false; // override the static default of 'true'  

A criação de cabeçalhos repetidos com controle de atributo dinâmico é permitida, conforme mostrado no código a seguir.

[MessageHeaderArray] public MessageHeader<Person> documentApprovers[];  

No lado do destinatário, a leitura desses atributos SOAP só poderá ser feita se a classe MessageHeader<T> for usada para o cabeçalho no tipo. Examine as propriedades Actor, Relay ou MustUnderstand de um cabeçalho do tipo MessageHeader<T> para descobrir as configurações de atributo na mensagem recebida.

Quando uma mensagem é recebida e enviada de volta, as configurações do atributo SOAP só fazem uma ida e volta para cabeçalhos do tipo MessageHeader<T>.

Ordem de partes do corpo SOAP

Em algumas circunstâncias, talvez seja necessário controlar a ordem das partes do corpo. A ordem dos elementos do corpo é alfabética por padrão, mas pode ser controlada pela propriedade MessageBodyMemberAttribute.Order. Essa propriedade tem a mesma semântica da propriedade DataMemberAttribute.Order, com exceção do comportamento em cenários de herança (em contratos de mensagem, os membros do corpo do tipo base não são classificados antes dos membros do corpo do tipo derivado). Para obter mais informações, confira Ordem do membro de dados.

No exemplo a seguir, amount normalmente virá primeiro, porque ele é o primeiro na ordem alfabética. No entanto, a propriedade Order o coloca na terceira posição.

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public Operation operation;  
  [MessageBodyMember(Order=1)] public Account sourceAccount;  
  [MessageBodyMember(Order=2)] public Account targetAccount;  
  [MessageBodyMember(Order=3)] public int amount;  
}  

Controle de versão do contrato de mensagem

Ocasionalmente, talvez seja necessário alterar os contratos de mensagens. Por exemplo, uma nova versão do aplicativo pode adicionar um cabeçalho extra a uma mensagem. Em seguida, ao fazer o envio da nova versão para a antiga, o sistema precisa lidar com um cabeçalho extra, bem como um cabeçalho ausente ao ir na outra direção.

As seguintes regras se aplicam aos cabeçalhos de controle de versão:

  • O WCF não se opõe aos cabeçalhos ausentes: os membros correspondentes são deixados com os valores padrão.

  • O WCF também ignora cabeçalhos extras inesperados. A única exceção a essa regra é se o cabeçalho extra tem um atributo MustUnderstand definido como true na mensagem SOAP de entrada, nesse caso, uma exceção será gerada porque um cabeçalho que precisa ser compreendido não pode ser processado.

Os corpos das mensagens têm regras de controle de versão semelhantes: as partes do corpo da mensagem ausentes e adicionais são ignoradas.

Considerações sobre herança

Um tipo de contrato de mensagem pode herdar de outro tipo, desde que o tipo base também tenha um contrato de mensagem.

Ao criar ou acessar uma mensagem usando um tipo de contrato de mensagem que herda de outros tipos de contrato de mensagem, as seguintes regras se aplicam:

  • Todos os cabeçalhos de mensagem na hierarquia de herança são coletados juntos para formar o conjunto completo de cabeçalhos da mensagem.

  • Todas as partes do corpo da mensagem na hierarquia de herança são coletadas juntas para formar o corpo completo da mensagem. As partes do corpo são ordenadas de acordo com as regras de ordenação comuns (pela propriedade MessageBodyMemberAttribute.Order e, em seguida, por ordem alfabética), sem relevância para o lugar na hierarquia de herança. É altamente desaconselhável usar a herança do contrato de mensagem quando as partes do corpo da mensagem ocorrem em vários níveis da árvore de herança. Se uma classe base e uma classe derivada definirem um cabeçalho ou uma parte do corpo com o mesmo nome, o membro da classe mais base será usado para armazenar o valor desse cabeçalho ou parte do corpo.

Considere as classes no exemplo de código a seguir.

[MessageContract]  
public class PersonRecord  
{  
  [MessageHeader(Name="ID")] public int personID;  
  [MessageBodyMember] public string patientName;  
}  
  
[MessageContract]  
public class PatientRecord : PersonRecord  
{  
  [MessageHeader(Name="ID")] public int patientID;  
  [MessageBodyMember] public string diagnosis;  
}  

A classe PatientRecord descreve uma mensagem com um cabeçalho chamado ID. O cabeçalho corresponde ao personID e não ao membro patientID, pois o membro mais base é escolhido. Portanto, o campo patientID é inútil neste caso. O corpo da mensagem contém o elemento diagnosis seguido do elemento patientName, porque essa é a ordem alfabética. Observe que o exemplo mostra um padrão altamente desaconselhável: tanto a base quanto os contratos de mensagem derivadas têm partes do corpo da mensagem.

Considerações sobre a linguagem WSDL

Ao gerar um contrato da linguagem WSDL de um serviço que usa contratos de mensagem, é importante lembrar que nem todos os recursos do contrato de mensagem são refletidos no WSDL resultante. Considere os seguintes pontos:

  • O WSDL não pode expressar o conceito de uma matriz de cabeçalhos. Ao criar mensagens com uma matriz de cabeçalhos com o MessageHeaderArrayAttribute, o WSDL resultante reflete apenas um cabeçalho em vez da matriz.

  • O documento WSDL resultante pode não refletir algumas informações de nível de proteção.

  • O tipo de mensagem gerado no WSDL tem o mesmo nome de classe do tipo de contrato de mensagem.

  • Quando o mesmo contrato de mensagem é usado em várias operações, vários tipos de mensagem são gerados no documento WSDL. Os nomes passam a ser exclusivos pela adição dos números "2", "3" etc., para usos posteriores. Quando o WSDL é importado novamente, vários tipos de contrato de mensagem são criados e são idênticos, exceto os nomes.

Considerações sobre a codificação SOAP

O WCF permite que você use o estilo de codificação SOAP herdado de XML, mas o uso dele não é recomendado. Quando esse estilo é usado (definindo a propriedade Use como Encoded no System.ServiceModel.XmlSerializerFormatAttribute aplicado ao contrato de serviço), as seguintes considerações adicionais se aplicam:

  • Não há suporte para os cabeçalhos de mensagem. Isso significa que o atributo MessageHeaderAttribute e o atributo MessageHeaderArrayAttribute de matriz são incompatíveis com a codificação SOAP.

  • Se o contrato de mensagem não estiver encapsulado, ou seja, se a propriedade IsWrapped estiver definida como false, o contrato de mensagem poderá ter apenas uma parte do corpo.

  • O nome do elemento wrapper do contrato de mensagem de solicitação precisa corresponder ao nome da operação. Use a propriedade WrapperName do contrato de mensagem para fazer isso.

  • O nome do elemento wrapper para o contrato de mensagem de resposta precisa igual ao nome da operação com o sufixo 'Response'. Use a propriedade WrapperName do contrato de mensagem para fazer isso.

  • A codificação SOAP preserva as referências de objeto. Por exemplo, considere o código a seguir.

    [MessageContract(WrapperName="updateChangeRecord")]  
    public class ChangeRecordRequest  
    {  
      [MessageBodyMember] Person changedBy;  
      [MessageBodyMember] Person changedFrom;  
      [MessageBodyMember] Person changedTo;  
    }  
    
    [MessageContract(WrapperName="updateChangeRecordResponse")]  
    public class ChangeRecordResponse  
    {  
      [MessageBodyMember] Person changedBy;  
      [MessageBodyMember] Person changedFrom;  
      [MessageBodyMember] Person changedTo;  
    }  
    
    // application code:  
    ChangeRecordRequest cr = new ChangeRecordRequest();  
    Person p = new Person("John Doe");  
    cr.changedBy=p;  
    cr.changedFrom=p;  
    cr.changedTo=p;  
    

Após a serialização da mensagem usando a codificação SOAP, changedFrom e changedTo não contêm cópias próprias de p, mas apontam para a cópia dentro do elemento changedBy.

Considerações sobre desempenho

Cada cabeçalho de mensagem e parte do corpo da mensagem é serializada independentemente das outras. Portanto, os mesmos namespaces podem ser declarados novamente para cada cabeçalho e parte do corpo. Para aprimorar o desempenho, especialmente em termos do tamanho da mensagem pela rede, consolide vários cabeçalhos e partes do corpo em um só cabeçalho ou parte do corpo. Por exemplo, em vez da seguinte sintaxe:

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public Operation operation;  
  [MessageBodyMember] public Account sourceAccount;  
  [MessageBodyMember] public Account targetAccount;  
  [MessageBodyMember] public int amount;  
}  

Use este código.

[MessageContract]  
public class BankingTransaction  
{  
  [MessageHeader] public Operation operation;  
  [MessageBodyMember] public OperationDetails details;  
}  
  
[DataContract]  
public class OperationDetails  
{  
  [DataMember] public Account sourceAccount;  
  [DataMember] public Account targetAccount;  
  [DataMember] public int amount;  
}  

Contratos de mensagens e assíncronos baseados em eventos

As diretrizes de design do modelo assíncrono baseado em eventos declaram que, se mais de um valor for retornado, um valor será retornado como a propriedade Result e os outros serão retornados como propriedades no objeto EventArgs. Um resultado é que, se um cliente importar metadados usando as opções de comando assíncrono com base em eventos, e a operação retornar mais de um valor, o objeto padrão EventArgs retornará um valor como a propriedade Result e os restantes serão propriedades do objeto EventArgs.

Se você desejar receber o objeto de mensagem como a propriedade Result e ter valores retornados como propriedades nesse objeto, use a opção de comando /messageContract. Isso gera uma assinatura que retorna a mensagem de resposta como a propriedade Result no objeto EventArgs. Todos os valores de retorno internos são propriedades do objeto da mensagem de resposta.

Confira também