Partilhar via


Projetando contratos de serviço

Este tópico descreve o que são contratos de serviço, como eles são definidos, quais operações estão disponíveis (e as implicações para as trocas de mensagens subjacentes), quais tipos de dados são usados e outros problemas que ajudam a projetar operações que satisfaçam os requisitos do seu cenário.

Criando um contrato de serviço

Os serviços expõem uma série de operações. Em aplicativos WCF (Windows Communication Foundation), defina as operações criando um método e marcando-o com o OperationContractAttribute atributo. Em seguida, para criar um contrato de serviço, agrupe suas operações, declarando-as em uma interface marcada com o ServiceContractAttribute atributo ou definindo-as em uma classe marcada com o mesmo atributo. (Para um exemplo básico, consulte Como: Definir um contrato de serviço.)

Todos os métodos que não têm um OperationContractAttribute atributo não são operações de serviço e não são expostos por serviços WCF.

Este tópico descreve os seguintes pontos de decisão ao projetar um contrato de serviço:

  • Se deve usar classes ou interfaces.

  • Como especificar os tipos de dados que você deseja trocar.

  • Os tipos de padrões de troca que você pode usar.

  • Se pode incluir requisitos de segurança explícitos no contrato.

  • As restrições para entradas e saídas de operação.

Classes ou interfaces

Ambas as classes e interfaces representam um agrupamento de funcionalidades e, portanto, ambas podem ser usadas para definir um contrato de serviço WCF. No entanto, é recomendável usar interfaces porque elas modelam diretamente contratos de serviço. Sem uma implementação, as interfaces não fazem mais do que definir um agrupamento de métodos com determinadas assinaturas. Implemente uma interface de contrato de serviço e você implementou um serviço WCF.

Todos os benefícios das interfaces gerenciadas se aplicam às interfaces de contrato de serviço:

  • As interfaces de contrato de serviço podem estender qualquer número de outras interfaces de contrato de serviço.

  • Uma única classe pode implementar qualquer número de contratos de serviço implementando essas interfaces de contrato de serviço.

  • Você pode modificar a implementação de um contrato de serviço alterando a implementação da interface, enquanto o contrato de serviço permanece o mesmo.

  • Você pode fazer a versão do seu serviço implementando a interface antiga e a nova. Os clientes antigos se conectam à versão original, enquanto os clientes mais novos podem se conectar à versão mais recente.

Nota

Ao herdar de outras interfaces de contrato de serviço, você não pode substituir propriedades de operação, como o nome ou namespace. Se você tentar fazer isso, crie uma nova operação no contrato de serviço atual.

Para obter um exemplo de como usar uma interface para criar um contrato de serviço, consulte Como criar um serviço com uma interface de contrato.

No entanto, você pode usar uma classe para definir um contrato de serviço e implementá-lo ao mesmo tempo. A vantagem de criar seus serviços aplicando ServiceContractAttribute e OperationContractAttribute diretamente à classe e aos métodos na classe, respectivamente, é a velocidade e a simplicidade. As desvantagens são que as classes gerenciadas não suportam herança múltipla e, como resultado, elas só podem implementar um contrato de serviço de cada vez. Além disso, qualquer modificação nas assinaturas de classe ou método modifica o contrato público para esse serviço, o que pode impedir que clientes não modificados usem seu serviço. Para obter mais informações, consulte Implementando contratos de serviço.

Para obter um exemplo que usa uma classe para criar um contrato de serviço e implementa-lo ao mesmo tempo, consulte Como criar um serviço com uma classe de contrato.

Neste ponto, você deve entender a diferença entre definir seu contrato de serviço usando uma interface e usando uma classe. O próximo passo é decidir quais dados podem ser passados de um lado para o outro entre um serviço e seus clientes.

Parâmetros e valores de retorno

Cada operação tem um valor de retorno e um parâmetro, mesmo que sejam void. No entanto, ao contrário de um método local, no qual você pode passar referências a objetos de um objeto para outro, as operações de serviço não passam referências a objetos. Em vez disso, eles passam cópias dos objetos.

Isso é significativo porque cada tipo usado em um parâmetro ou valor de retorno deve ser serializável; ou seja, deve ser possível converter um objeto desse tipo em um fluxo de bytes e de um fluxo de bytes em um objeto.

Os tipos primitivos são serializáveis por padrão, assim como muitos tipos no .NET Framework.

Nota

O valor dos nomes dos parâmetros na assinatura da operação fazem parte do contrato e diferenciam maiúsculas de minúsculas. Se você quiser usar o mesmo nome de parâmetro localmente, mas modificar o nome nos metadados publicados, consulte o System.ServiceModel.MessageParameterAttribute.

Contratos de Dados

Os aplicativos orientados a serviços, como os aplicativos WCF (Windows Communication Foundation), são projetados para interoperar com o maior número possível de aplicativos cliente em plataformas Microsoft e não-Microsoft. Para a maior interoperabilidade possível, é recomendável marcar seus tipos com os DataContractAttribute atributos e DataMemberAttribute para criar um contrato de dados, que é a parte do contrato de serviço que descreve os dados que suas operações de serviço trocam.

Os contratos de dados são contratos de estilo opt-in: nenhum tipo ou membro de dados é serializado a menos que você aplique explicitamente o atributo de contrato de dados. Os contratos de dados não estão relacionados ao escopo de acesso do código gerenciado: os membros de dados privados podem ser serializados e enviados para outro lugar para serem acessados publicamente. (Para um exemplo básico de um contrato de dados, consulte Como criar um contrato de dados básico para uma classe ou estrutura.) WCF lida com a definição das mensagens SOAP subjacentes que permitem a funcionalidade da operação, bem como a serialização de seus tipos de dados dentro e fora do corpo das mensagens. Contanto que seus tipos de dados sejam serializáveis, você não precisa pensar na infraestrutura de troca de mensagens subjacente ao projetar suas operações.

Embora o aplicativo WCF típico use os DataContractAttribute atributos e DataMemberAttribute para criar contratos de dados para operações, você pode usar outros mecanismos de serialização. O padrão ISerializable, SerializableAttributee IXmlSerializable todos os mecanismos funcionam para lidar com a serialização de seus tipos de dados nas mensagens SOAP subjacentes que os transportam de um aplicativo para outro. Você pode empregar mais estratégias de serialização se seus tipos de dados exigirem suporte especial. Para obter mais informações sobre as opções de serialização de tipos de dados em aplicativos WCF, consulte Especificando transferência de dados em contratos de serviço.

Mapeando parâmetros e valores de retorno para trocas de mensagens

As operações de serviço são suportadas por uma troca subjacente de mensagens SOAP que transferem dados do aplicativo para frente e para trás, além dos dados exigidos pelo aplicativo para oferecer suporte a determinados recursos padrão de segurança, transação e relacionados à sessão. Como esse é o caso, a assinatura de uma operação de serviço dita um determinado padrão de troca de mensagens subjacente (MEP) que pode suportar a transferência de dados e os recursos que uma operação requer. Você pode especificar três padrões no modelo de programação do WCF: solicitação/resposta, unidirecional e padrões de mensagem duplex.

Pedido/Resposta

Um padrão de solicitação/resposta é aquele no qual um remetente de solicitação (um aplicativo cliente) recebe uma resposta com a qual a solicitação está correlacionada. Este é o MEP padrão porque suporta uma operação na qual um ou mais parâmetros são passados para a operação e um valor de retorno é passado de volta para o chamador. Por exemplo, o exemplo de código C# a seguir mostra uma operação de serviço básica que usa uma cadeia de caracteres e retorna uma cadeia de caracteres.

[OperationContractAttribute]  
string Hello(string greeting);  

A seguir está o código equivalente do Visual Basic.

<OperationContractAttribute()>  
Function Hello (ByVal greeting As String) As String  

Esta assinatura de operação dita a forma de troca de mensagens subjacente. Se não houver correlação, o WCF não poderá determinar para qual operação o valor de retorno é destinado.

Observe que, a menos que você especifique um padrão de mensagem subjacente diferente, até mesmo as operações de serviço que retornam void (Nothing no Visual Basic) são trocas de mensagens de solicitação/resposta. O resultado para sua operação é que, a menos que um cliente invoque a operação de forma assíncrona, o cliente interrompe o processamento até que a mensagem de retorno seja recebida, mesmo que essa mensagem esteja vazia no caso normal. O exemplo de código C# a seguir mostra uma operação que não retorna até que o cliente tenha recebido uma mensagem vazia em resposta.

[OperationContractAttribute]  
void Hello(string greeting);  

A seguir está o código equivalente do Visual Basic.

<OperationContractAttribute()>  
Sub Hello (ByVal greeting As String)  

O exemplo anterior pode diminuir o desempenho e a capacidade de resposta do cliente se a operação demorar muito tempo para ser executada, mas há vantagens em operações de solicitação/resposta, mesmo quando elas retornam void. A mais óbvia é que falhas SOAP podem ser retornadas na mensagem de resposta, o que indica que alguma condição de erro relacionada ao serviço ocorreu, seja na comunicação ou no processamento. As falhas SOAP especificadas em um contrato de serviço são passadas para o aplicativo cliente como um FaultException<TDetail> objeto, onde o parâmetro type é o tipo especificado no contrato de serviço. Isso facilita a notificação de clientes sobre condições de erro nos serviços WCF. Para obter mais informações sobre exceções, falhas SOAP e tratamento de erros, consulte Especificando e manipulando falhas em contratos e serviços. Para ver um exemplo de um serviço de solicitação/resposta e cliente, consulte Como criar um contrato de solicitação-resposta. Para obter mais informações sobre problemas com o padrão solicitação-resposta, consulte Serviços de solicitação-resposta.

Só de ida

Se o cliente de um aplicativo de serviço WCF não deve esperar que a operação seja concluída e não processar falhas SOAP, a operação pode especificar um padrão de mensagem unidirecional. Uma operação unidirecional é aquela na qual um cliente invoca uma operação e continua o processamento depois que o WCF grava a mensagem na rede. Normalmente, isso significa que, a menos que os dados que estão sendo enviados na mensagem de saída sejam extremamente grandes, o cliente continua em execução quase imediatamente (a menos que haja um erro ao enviar os dados). Esse tipo de padrão de troca de mensagens oferece suporte a um comportamento semelhante a um evento de um cliente para um aplicativo de serviço.

Uma troca de mensagens em que uma mensagem é enviada e nenhuma é recebida não pode suportar uma operação de serviço que especifica um valor de retorno diferente de void; neste caso, uma InvalidOperationException exceção é lançada.

Nenhuma mensagem de retorno também significa que não pode haver nenhuma falha SOAP retornada para indicar quaisquer erros no processamento ou comunicação. (A comunicação de informações de erro quando as operações são unidirecionais requer um padrão de troca de mensagens duplex.)

Para especificar uma troca de mensagem unidirecional para uma operação que retorna void, defina a IsOneWay propriedade como true, como no exemplo de código C# a seguir.

[OperationContractAttribute(IsOneWay=true)]  
void Hello(string greeting);  

A seguir está o código equivalente do Visual Basic.

<OperationContractAttribute(IsOneWay := True)>  
Sub Hello (ByVal greeting As String)  

Esse método é idêntico ao exemplo de solicitação/resposta anterior, mas definir a IsOneWay propriedade para true significa que, embora o método seja idêntico, a operação de serviço não envia uma mensagem de retorno e os clientes retornam imediatamente após a mensagem de saída ter sido entregue à camada de canal. Para obter um exemplo, consulte Como criar um contrato unidirecional. Para obter mais informações sobre o padrão unidirecional, consulte Serviços unidirecionais.

Duplex

Um padrão duplex é caracterizado pela capacidade do serviço e do cliente de enviar mensagens entre si de forma independente, seja usando mensagens unidirecionais ou de solicitação/resposta. Essa forma de comunicação bidirecional é útil para serviços que devem se comunicar diretamente com o cliente ou para fornecer uma experiência assíncrona para ambos os lados de uma troca de mensagens, incluindo comportamento semelhante a um evento.

O padrão duplex é um pouco mais complexo do que os padrões de solicitação/resposta ou unidirecionais devido ao mecanismo adicional de comunicação com o cliente.

Para criar um contrato duplex, você também deve projetar um contrato de retorno de chamada e atribuir o tipo desse contrato de retorno de chamada à CallbackContract propriedade do atributo que marca seu ServiceContractAttribute contrato de serviço.

Para implementar um padrão duplex, você deve criar uma segunda interface que contenha as declarações de método que são chamadas no cliente.

Para obter um exemplo de criação de um serviço e um cliente que acessa esse serviço, consulte Como criar um contrato duplex e Como acessar serviços com um contrato duplex. Para obter um exemplo de trabalho, consulte Duplex. Para obter mais informações sobre problemas relacionados ao uso de contratos duplex, consulte Serviços duplex.

Atenção

Quando um serviço recebe uma mensagem duplex, ele examina o ReplyTo elemento nessa mensagem de entrada para determinar para onde enviar a resposta. Se o canal usado para receber a mensagem não estiver protegido, um cliente não confiável poderá enviar uma mensagem mal-intencionada com a de uma máquina ReplyTode destino, levando a uma negação de serviço (DOS) dessa máquina de destino.

Parâmetros out e Ref

Na maioria dos casos, você pode usar in parâmetros (ByVal no Visual Basic) e outref parâmetros (ByRef no Visual Basic). Como ambos os parâmetros e ref indicam que os out dados são retornados de uma operação, uma assinatura de operação como a seguir especifica que uma operação de solicitação/resposta é necessária mesmo que a assinatura da operação retorne void.

[ServiceContractAttribute]  
public interface IMyContract  
{  
  [OperationContractAttribute]  
  public void PopulateData(ref CustomDataType data);  
}  

A seguir está o código equivalente do Visual Basic.

<ServiceContractAttribute()> _  
Public Interface IMyContract  
  <OperationContractAttribute()> _  
  Public Sub PopulateData(ByRef data As CustomDataType)  
End Interface  

As únicas exceções são os casos em que a sua assinatura tem uma estrutura específica. Por exemplo, você pode usar a NetMsmqBinding associação para se comunicar com clientes somente se o método usado para declarar uma operação retornar void: não pode haver nenhum valor de saída, seja um valor de retorno, refou out parâmetro.

Além disso, o uso out de parâmetros or ref requer que a operação tenha uma mensagem de resposta subjacente para transportar de volta o objeto modificado. Se sua operação for unidirecional, uma InvalidOperationException exceção será lançada em tempo de execução.

Especificar o nível de proteção de mensagens no contrato

Ao projetar seu contrato, você também deve decidir o nível de proteção de mensagens dos serviços que implementam seu contrato. Isso é necessário somente se a segurança da mensagem for aplicada à vinculação no ponto de extremidade do contrato. Se a vinculação tiver a segurança desativada (ou seja, se a associação fornecida pelo sistema definir o System.ServiceModel.SecurityMode valor SecurityMode.None), não será necessário decidir sobre o nível de proteção de mensagem para o contrato. Na maioria dos casos, as ligações fornecidas pelo sistema com segurança no nível da mensagem aplicada fornecem um nível de proteção suficiente e você não precisa considerar o nível de proteção para cada operação ou para cada mensagem.

O nível de proteção é um valor que especifica se as mensagens (ou partes da mensagem) que suportam um serviço são assinadas, assinadas e criptografadas ou enviadas sem assinaturas ou criptografia. O nível de proteção pode ser definido em vários escopos: no nível de serviço, para uma operação específica, para uma mensagem dentro dessa operação ou uma parte da mensagem. Os valores definidos em um escopo tornam-se o valor padrão para escopos menores, a menos que sejam explicitamente substituídos. Se uma configuração de vinculação não puder fornecer o nível mínimo de proteção exigido para o contrato, uma exceção será lançada. E quando nenhum valor de nível de proteção é explicitamente definido no contrato, a configuração de vinculação controla o nível de proteção para todas as mensagens se a associação tiver segurança de mensagem. Este é o comportamento predefinido.

Importante

Decidir se vários escopos de um contrato devem ser explicitamente definidos para menos do que o nível de proteção total é ProtectionLevel.EncryptAndSign geralmente uma decisão que troca algum grau de segurança por um melhor desempenho. Nesses casos, suas decisões devem girar em torno de suas operações e do valor dos dados que elas trocam. Para obter mais informações, consulte Protegendo serviços.

Por exemplo, o exemplo de código a seguir não define a propriedade ou a ProtectionLevelProtectionLevel propriedade no contrato.

[ServiceContract]  
public interface ISampleService  
{  
  [OperationContractAttribute]  
  public string GetString();  
  
  [OperationContractAttribute]  
  public int GetInt();
}  

A seguir está o código equivalente do Visual Basic.

<ServiceContractAttribute()> _  
Public Interface ISampleService  
  
  <OperationContractAttribute()> _  
  Public Function GetString()As String  
  
  <OperationContractAttribute()> _  
  Public Function GetData() As Integer  
  
End Interface  

Ao interagir com uma ISampleService implementação em um ponto de extremidade com um padrão WSHttpBinding (o padrão System.ServiceModel.SecurityMode, que é Message), todas as mensagens são criptografadas e assinadas porque este é o nível de proteção padrão. No entanto, quando um ISampleService serviço é usado com um padrão BasicHttpBinding (o padrão SecurityMode, que é None), todas as mensagens são enviadas como texto porque não há segurança para essa ligação e, portanto, o nível de proteção é ignorado (ou seja, as mensagens não são criptografadas nem assinadas). Se o SecurityMode foi alterado para Message, então essas mensagens seriam criptografadas e assinadas (porque esse seria agora o nível de proteção padrão da ligação).

Se você quiser especificar ou ajustar explicitamente os requisitos de proteção para seu contrato, defina a ProtectionLevel propriedade (ou qualquer uma das propriedades em um escopo menor) para o nível exigido pelo contrato de ProtectionLevel serviço. Nesse caso, o uso de uma configuração explícita requer que a associação ofereça suporte a essa configuração no mínimo para o escopo usado. Por exemplo, o exemplo de código a seguir especifica um ProtectionLevel valor explicitamente, para a GetGuid operação.

[ServiceContract]  
public interface IExplicitProtectionLevelSampleService  
{  
  [OperationContractAttribute]  
  public string GetString();  
  
  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.None)]  
  public int GetInt();
  [OperationContractAttribute(ProtectionLevel=ProtectionLevel.EncryptAndSign)]  
  public int GetGuid();
}  

A seguir está o código equivalente do Visual Basic.

<ServiceContract()> _
Public Interface IExplicitProtectionLevelSampleService
    <OperationContract()> _
    Public Function GetString() As String
    End Function
  
    <OperationContract(ProtectionLevel := ProtectionLevel.None)> _
    Public Function GetInt() As Integer
    End Function
  
    <OperationContractAttribute(ProtectionLevel := ProtectionLevel.EncryptAndSign)> _
    Public Function GetGuid() As Integer
    End Function
  
End Interface  

Um serviço que implementa esse IExplicitProtectionLevelSampleService contrato e tem um ponto de extremidade que usa o padrão WSHttpBinding (o padrão System.ServiceModel.SecurityMode, que é Message) tem o seguinte comportamento:

  • As GetString mensagens de operação são criptografadas e assinadas.

  • As GetInt mensagens de operação são enviadas como texto não criptografado e não assinado (ou seja, sem formatação).

  • A GetGuid operação System.Guid é retornada em uma mensagem criptografada e assinada.

Para obter mais informações sobre os níveis de proteção e como usá-los, consulte Noções básicas sobre o nível de proteção. Para obter mais informações sobre segurança, consulte Protegendo serviços.

Outros requisitos de assinatura da operação

Alguns recursos do aplicativo exigem um tipo específico de assinatura de operação. Por exemplo, a NetMsmqBinding vinculação suporta serviços e clientes duráveis, nos quais um aplicativo pode reiniciar no meio da comunicação e continuar de onde parou sem perder nenhuma mensagem. (Para obter mais informações, consulte Filas no WCF.) No entanto, as operações duráveis devem ter apenas um in parâmetro e não ter valor de retorno.

Outro exemplo é o uso de Stream tipos em operações. Como o Stream parâmetro inclui todo o corpo da mensagem, se uma entrada ou saída (ou seja, ref parâmetro, out parâmetro ou valor de retorno) for do tipo Stream, ela deverá ser a única entrada ou saída especificada em sua operação. Além disso, o parâmetro ou tipo de retorno deve ser Stream, System.ServiceModel.Channels.Messageou System.Xml.Serialization.IXmlSerializable. Para obter mais informações sobre fluxos, consulte Dados grandes e streaming.

Nomes, namespaces e ofuscação

Os nomes e namespaces dos tipos .NET na definição de contratos e operações são significativos quando os contratos são convertidos em WSDL e quando as mensagens de contrato são criadas e enviadas. Portanto, é altamente recomendável que os nomes e namespaces de contrato de serviço sejam explicitamente definidos usando as Name propriedades e Namespace de todos os atributos de contrato de suporte, OperationContractAttributecomo , ServiceContractAttribute, DataContractAttribute, DataMemberAttributee outros atributos de contrato.

Um resultado disso é que, se os nomes e namespaces não forem definidos explicitamente, o uso de ofuscação de IL no assembly alterará os nomes de tipo de contrato e namespaces e resultará em WSDL modificado e trocas de fio que normalmente falham. Se você não definir os nomes de contrato e namespaces explicitamente, mas pretender usar ofuscação, use os ObfuscationAttribute atributos e ObfuscateAssemblyAttribute para impedir a modificação dos nomes de tipo de contrato e namespaces.

Consulte também