Criando contratos de serviço

Este tópico descreve quais são os 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 você a criar operações que atendam aos requisitos do seu cenário.

Criando contratos de serviço

Os serviços mostram várias operações. Em aplicativos WCF (Windows Communication Foundation), defina as operações criando um método e marcando-o com o atributo OperationContractAttribute. Em seguida, para criar um contrato de serviço, agrupe suas operações, seja declarando-as dentro de uma interface marcada com o atributo ServiceContractAttribute 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 atributo OperationContractAttribute não são operações de serviço e não são expostos pelos serviços do WCF.

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

  • Se você deve usar classes ou interfaces.

  • Como especificar os tipos de dados a serem trocados.

  • Os tipos de padrões de troca que podem ser usados.

  • Se você pode esclarecer os requisitos de segurança que fazem parte do contrato.

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

Classes ou interfaces

As classes e as interfaces representam um agrupamento de funcionalidades e, portanto, as duas podem ser usadas para definir um contrato de serviço de WCF. No entanto, é recomendado que você use interfaces porque elaboram diretamente os contratos de serviço. Sem uma implementação, as interfaces apenas definem um agrupamento de métodos com determinadas assinaturas. Implemente uma interface de contrato de serviço e você implementa um serviço WCF.

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

  • As interfaces de contrato de serviço podem se estender a qualquer interface de contrato de serviço.

  • Uma única classe pode implementar qualquer quantidade de contratos de serviço com a implementação dessas interfaces de contrato de serviço.

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

  • Você pode elaborar uma versão do serviço implementando a interface antiga e a nova. Os clientes antigos se conectam à versão original, enquanto que os clientes mais recentes podem se conectar à versão mais nova.

Observação

Com a herança de outras interfaces de contrato de serviço, você não pode substituir as propriedades de operação, como nome ou namespace. Se tentar substituir, você cria 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 implementar esse contrato ao mesmo tempo. As vantagens da criação de seus serviços aplicando ServiceContractAttribute e OperationContractAttribute diretamente à classe e aos métodos na classe são, respectivamente, a velocidade e a simplicidade. As desvantagens são que as classes gerenciadas não oferecem suporte a várias heranças e, assim, somente podem implementar um contrato de serviço por vez. Além disso, qualquer modificação nas assinaturas de classe ou método altera 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 ver um exemplo que usa uma classe para criar um contrato de serviço e, ao mesmo tempo, implementá-lo, 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 com uma interface e com uma classe. A próxima etapa é decidir os dados que podem ser passados entre um serviço e seus clientes.

Parâmetros e valores retornados

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

Essa questão é importante porque cada tipo usado em um parâmetro ou valor retornado 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.

Observação

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

Contratos de dados

Aplicativos orientados a serviços, como aplicativos WCF (Windows Communication Foundation), foram criados para ativar o maior número possível de aplicativos de clientes nas plataformas Microsoft e que não são Microsoft. Para conseguir a maior interoperabilidade possível, recomenda-se marcar seus tipos com os atributos DataContractAttribute e DataMemberAttribute para criar um contrato de dados, que faz parte do contrato de serviço que descreve os dados de troca de suas operações de serviço.

Os contratos de dados são contratos de aceitação: 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 local para serem acessados publicamente. (Para ver um exemplo básico de um contrato de dados, consulte Como criar um contrato de dados básico para uma classe ou estrutura.) O WCF trata 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. Desde que seus tipos de dados sejam serializáveis, não é preciso pensar na infraestrutura de troca de mensagens subjacentes ao criar as operações.

Embora o aplicativo WCF normal use os atributos DataContractAttributee DataMemberAttribute para criar contratos de dados para operações, você pode usar outros mecanismos de serialização. Todos os mecanismos padrão ISerializable, SerializableAttribute e IXmlSerializable funcionam de forma a tratar a serialização de seus tipos de dados nas mensagens SOAP subjacentes que são levadas 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 nos aplicativos WCF, consulte Especificando a transferência de dados em contratos de serviço.

Mapeando parâmetros e valores retornados para trocas de mensagens

As operações de serviço têm suporte de uma troca de mensagens SOAP subjacente, que transferem dados do aplicativo alternadamente, além dos dados exigidos pelo aplicativo que oferecem suporte a alguns recursos relacionados à sessão, segurança e transação. Como é o caso, a assinatura de uma operação de serviço determina um padrão de troca de mensagens subjacente (MEP) que pode oferecer suporte à transferência de dados e aos recursos necessários por uma operação. Você pode especificar três padrões no modelo de programação do WCF: padrões de mensagem de solicitação/resposta, unidirecional e duplex.

Solicitação/Resposta

Um padrão de solicitação/resposta é aquele em que um remetente de solicitação (um aplicativo cliente) recebe uma resposta com a qual a solicitação está correlacionada. Esse é o MEP padrão porque oferece suporte a uma operação na qual um ou mais parâmetros são passados para a operação e um valor retornado retorna 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);  

Veja a seguir o código equivalente do Visual Basic.

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

Essa assinatura de operação estabelece a forma de troca de mensagens subjacente. Se não houver correlação, o WCF não pode determinar qual é o destino do valor retornado da operação.

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 da operação é que, a menos que um cliente recorra à 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 na resposta.

[OperationContractAttribute]  
void Hello(string greeting);  

Veja a seguir 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 levar muito tempo para ser executada, mas há vantagens nas operações de solicitação/resposta mesmo quando retornam void. A mais evidente é que as falhas SOAP podem ser retornadas na mensagem de resposta, o que indica que ocorreu algum erro relacionado ao serviço, seja na comunicação ou no processamento. As falhas SOAP especificadas em um contrato de serviço são passadas para o aplicativo do cliente como um objeto FaultException<TDetail>, em que o parâmetro de tipo é o tipo especificado no contrato de serviço. Isso facilita a notificação dos clientes sobre as condições de erro nos serviços WCF. Para obter mais informações sobre as exceções, as falhas SOAP e o tratamento de erros, consulte Especificando e tratando falhas nos 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 de solicitação-resposta, consulte Serviços de Solicitação-Resposta.

Unidirecional

Se o cliente de um aplicativo de serviço WCF não puder aguardar a conclusão da operação e não processar falhas SOAP, a operação pode especificar um padrão de mensagem unidirecional. Uma operação unidirecional é aquela em que um cliente requer 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 enviados na mensagem de saída sejam extremamente grandes, o cliente continua sendo executado quase imediatamente (a menos que haja um erro ao enviar os dados). Esse tipo de padrão de troca de mensagens oferece suporte a comportamentos semelhantes a eventos de um cliente para um aplicativo de serviço.

Uma troca de mensagens na qual uma mensagem é enviada e nenhuma é recebida, não pode oferecer suporte a uma operação de serviço que especifica um valor retornado diferente de void; nesse caso, uma exceção InvalidOperationException é gerada.

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

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

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

Veja a seguir 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. No entanto, definir a propriedade IsOneWay como 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 depois que a mensagem de saída é entregue à camada de canal. Para ver 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 um para o outro de forma independente, seja usando mensagens unidirecionais ou de solicitação/resposta. Essa forma de comunicação bidirecional é útil para serviços que precisam se comunicar diretamente com o cliente ou fornecer uma experiência assíncrona para os dois lados em 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 unidirecional devido ao mecanismo adicional de comunicação com o cliente.

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

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

Para ver 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 ver um exemplo de funcionamento, consulte Duplex. Para obter mais informações sobre problemas com os contratos duplex, consulte Serviços duplex.

Cuidado

Quando um serviço recebe uma mensagem duplex, é verificado o elemento ReplyTo na mensagem de entrada para determinar para onde enviar a resposta. Se o canal não estiver protegido, um cliente não confiável pode enviar uma mensagem mal-intencionada com um ReplyTo do computador de destino, o que gera uma negação do serviço no computador de destino.

Parâmetros out e ref

Na maioria dos casos, você pode usar parâmetros in (ByVal no Visual Basic) e out e parâmetros ref (ByRef no Visual Basic). Como os dois parâmetros out e ref indicam que os dados são retornados de uma operação, a assinatura de operação 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);  
}  

Veja a seguir 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 sua assinatura tem uma estrutura específica. Por exemplo, você pode usar a associação NetMsmqBinding para se comunicar com clientes mas apenas se o método usado para declarar uma operação retornar void. Não pode haver nenhum valor de saída, seja um valor retornado, parâmetro ref ou out.

Além disso, o uso dos parâmetros out ou ref exigem que a operação tenha uma mensagem de resposta subjacente para devolver o objeto modificado. Se sua operação for uma operação unidirecional, uma exceção InvalidOperationException é gerada no tempo de execução.

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

Ao criar o contrato, você também deve decidir o nível de proteção de mensagem dos serviços implementados no seu contrato. Somente é necessário se a segurança da mensagem for aplicada à associação no ponto de extremidade do contrato. Se a segurança da associação for desativada (ou seja, se a associação fornecida pelo sistema definir System.ServiceModel.SecurityMode para o valor SecurityMode.None), você não precisa decidir sobre o nível de proteção de mensagem do contrato. Na maioria dos casos, as associações fornecidas pelo sistema com segurança a nível de mensagem aplicada oferecem 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 de mensagem) que oferecem suporte a um serviço são 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 do 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 substituídos explicitamente. Se uma configuração de associação não puder oferecer o nível mínimo de proteção necessário para o contrato, será gerada uma exceção. E, quando nenhum valor de nível de proteção é definido explicitamente no contrato, a configuração de associação controla o nível de proteção para todas as mensagens se a associação tiver segurança de mensagem. Esse é o comportamento padrão.

Importante

A decisão de definir explicitamente vários escopos de um contrato para menos que o nível de proteção total de ProtectionLevel.EncryptAndSign é geralmente uma decisão que tem algum grau de segurança pelo aumento do desempenho. Nesses casos, suas decisões devem girar em torno das operações e do valor dos dados trocados. Para obter mais informações, confira Como proteger serviços.

Por exemplo, o exemplo de código a seguir não define nem a propriedade ProtectionLevel ou ProtectionLevel do contrato.

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

Veja a seguir 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 implementação ISampleService 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 esse é o nível de proteção padrão. No entanto, quando um serviço ISampleService é 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 associação e, portanto, o nível de proteção é ignorado (ou seja, as mensagens não são criptografadas nem assinadas). Se SecurityMode fosse alterado para Message, essas mensagens seriam criptografadas e assinadas (porque seria o nível de proteção padrão da associação).

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

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

Veja a seguir 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 contrato IExplicitProtectionLevelSampleService 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 mensagens de operação GetString são criptografadas e assinadas.

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

  • A operação GetGuidSystem.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 Proteção de serviços.

Outros requisitos de assinatura de operação

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

Outro exemplo é o uso de tipos Stream em operações. Como o parâmetro Stream inclui todo o corpo da mensagem, se uma entrada ou uma saída (ou seja, parâmetro ref, parâmetro out ou valor retornado) for do tipo Stream, ele deve 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.Message ou System.Xml.Serialization.IXmlSerializable. Para saber mais sobre fluxos, confira Grandes dados 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 os namespaces do contrato de serviço sejam definidos explicitamente usando as propriedades Name e Namespace de todos os atributos de contrato de suporte, tais como ServiceContractAttribute, OperationContractAttribute, DataContractAttribute, DataMemberAttribute e outros atributos de contrato.

O resultado é que, se os nomes e namespaces não forem definidos explicitamente, o uso da ofuscação de IL no assembly altera os nomes e namespaces de tipo de contrato e resulta em trocas de WSDL e de fio modificadas que normalmente têm falhas. Se você não definir explicitamente os nomes e namespaces do contrato, mas pretende usar ofuscação, use os atributos ObfuscationAttribute e ObfuscateAssemblyAttribute para impedir a modificação dos nomes e namespaces de tipo de contrato.

Confira também