Compartilhar via


Especificando a transferência de dados em contratos de serviço

O WCF (Windows Communication Foundation) pode ser considerado uma infraestrutura de mensagens. As operações de serviço podem receber mensagens, processá-las e enviar mensagens. As mensagens são descritas usando contratos de operação. Por exemplo, considere o contrato a seguir.

[ServiceContract]  
public interface IAirfareQuoteService  
{  
    [OperationContract]  
    float GetAirfare(string fromCity, string toCity);  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
  
    <OperationContract()>  
    Function GetAirfare(fromCity As String, toCity As String) As Double  
End Interface  

Aqui, a GetAirfare operação aceita uma mensagem com informações sobre fromCity e toCityretorna uma mensagem que contém um número.

Este tópico explica as várias maneiras pelas quais um contrato de operação pode descrever mensagens.

Descrevendo mensagens usando parâmetros

A maneira mais simples de descrever uma mensagem é usar uma lista de parâmetros e o valor retornado. No exemplo anterior, os parâmetros de cadeia de caracteres fromCity e toCity foram usados para descrever a mensagem de solicitação, e o valor de retorno do tipo float foi usado para descrever a mensagem de resposta. Se o valor retornado sozinho não for suficiente para descrever uma mensagem de resposta, os parâmetros externos poderão ser usados. Por exemplo, a operação a seguir tem fromCity e toCity em sua mensagem de solicitação, e um número junto com uma moeda em sua mensagem de resposta:

[OperationContract]  
float GetAirfare(string fromCity, string toCity, out string currency);  
<OperationContract()>  
    Function GetAirfare(fromCity As String, toCity As String) As Double  

Além disso, você pode usar parâmetros de referência para tornar um parâmetro parte tanto da solicitação quanto da mensagem de resposta. Os parâmetros devem ser de tipos que podem ser serializados (convertidos em XML). Por padrão, o WCF usa um componente chamado classe DataContractSerializer para executar essa conversão. Há suporte para a maioria dos tipos primitivos (como int, string, floate DateTime.) . Os tipos definidos pelo usuário normalmente devem ter um contrato de dados. Para obter mais informações, consulte Como usar contratos de dados.

public interface IAirfareQuoteService  
{  
    [OperationContract]  
    float GetAirfare(Itinerary itinerary, DateTime date);  
  
    [DataContract]  
    public class Itinerary  
    {  
        [DataMember]  
        public string fromCity;  
        [DataMember]  
        public string toCity;  
   }  
}  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    GetAirfare(itinerary as Itinerary, date as DateTime) as Double  
  
    <DataContract()>  
    Class Itinerary  
  
        <DataMember()>  
        Public fromCity As String  
        <DataMember()>  
        Public toCity As String  
    End Class  
End Interface  

Ocasionalmente, o DataContractSerializer não é adequado para serializar seus tipos. O WCF dá suporte a um mecanismo de serialização alternativo, o XmlSerializer, que você também pode usar para serializar parâmetros. O XmlSerializer permite que você exerça mais controle sobre o XML resultante utilizando atributos como XmlAttributeAttribute. Para alternar para o uso de XmlSerializer em uma operação específica ou para todo o serviço, aplique o atributo XmlSerializerFormatAttribute a uma operação ou a um serviço. Por exemplo:

[ServiceContract]  
public interface IAirfareQuoteService  
{  
    [OperationContract]  
    [XmlSerializerFormat]  
    float GetAirfare(Itinerary itinerary, DateTime date);  
}  
public class Itinerary  
{  
    public string fromCity;  
    public string toCity;  
    [XmlAttribute]  
    public bool isFirstClass;  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    <XmlSerializerFormat>  
    GetAirfare(itinerary as Itinerary, date as DateTime) as Double  
  
End Interface  
  
Class Itinerary  
  
    Public fromCity As String  
    Public toCity As String  
    <XmlSerializerFormat()>  
    Public isFirstClass As Boolean  
End Class  

Para obter mais informações, consulte Como usar a classe XmlSerializer. Lembre-se de que alternar manualmente para o XmlSerializer mostrado aqui não é recomendado, a menos que você tenha motivos específicos para fazer isso conforme detalhado nesse tópico.

Para isolar nomes de parâmetro .NET de nomes de contrato, você pode usar o MessageParameterAttribute atributo e usar a Name propriedade para definir o nome do contrato. Por exemplo, o contrato de operação a seguir é equivalente ao primeiro exemplo neste tópico.

[OperationContract]  
public float GetAirfare(  
    [MessageParameter(Name="fromCity")] string originCity,  
    [MessageParameter(Name="toCity")] string destinationCity);  
<OperationContract()>  
  Function GetAirfare(<MessageParameter(Name := "fromCity")> fromCity As String, <MessageParameter(Name := "toCity")> toCity As String) As Double  

Descrever mensagens vazias

Uma mensagem de solicitação vazia pode ser descrita sem parâmetros de entrada ou referência. Por exemplo, em C#:

[OperationContract]

public int GetCurrentTemperature();

Por exemplo, no Visual Basic:

<OperationContract()>

Function GetCurrentTemperature() as Integer

Uma mensagem de resposta vazia pode ser descrita por ter um void tipo de retorno e nenhum parâmetro de saída ou referência. Por exemplo, em:

[OperationContract]  
public void SetTemperature(int temperature);  
<OperationContract()>  
Sub SetTemperature(temperature As Integer)  

Isso é diferente de uma operação unidirecional, como:

[OperationContract(IsOneWay=true)]  
public void SetLightbulbStatus(bool isOn);  
<OperationContract(IsOneWay:=True)>  
Sub SetLightbulbStatus(isOne As Boolean)  

A SetTemperatureStatus operação retorna uma mensagem vazia. Em vez disso, ele poderá retornar uma falha se houver um problema ao processar a mensagem de entrada. A SetLightbulbStatus operação não retorna nada. Não há como comunicar uma condição de falha desta operação.

Descrevendo mensagens usando contratos de mensagem

Talvez você queira usar um único tipo para representar a mensagem inteira. Embora seja possível usar um contrato de dados para essa finalidade, a maneira recomendada de fazer isso é usar um contrato de mensagem, o que evita níveis desnecessários de encapsulamento no XML resultante. Além disso, os contratos de mensagem permitem que você exercite mais controle sobre mensagens resultantes. Por exemplo, você pode decidir quais informações devem estar no corpo da mensagem e quais devem estar nos cabeçalhos da mensagem. O exemplo a seguir mostra o uso de contratos de mensagem.

[ServiceContract]  
public interface IAirfareQuoteService  
{  
    [OperationContract]  
    GetAirfareResponse GetAirfare(GetAirfareRequest request);  
}  
  
[MessageContract]  
public class GetAirfareRequest  
{  
    [MessageHeader] public DateTime date;  
    [MessageBodyMember] public Itinerary itinerary;  
}  
  
[MessageContract]  
public class GetAirfareResponse  
{  
    [MessageBodyMember] public float airfare;  
    [MessageBodyMember] public string currency;  
}  
  
[DataContract]  
public class Itinerary  
{  
    [DataMember] public string fromCity;  
    [DataMember] public string toCity;  
}  
<ServiceContract()>  
Public Interface IAirfareQuoteService  
    <OperationContract()>  
    Function GetAirfare(request As GetAirfareRequest) As GetAirfareResponse  
End Interface  
  
<MessageContract()>  
Public Class GetAirfareRequest  
    <MessageHeader()>
    Public Property date as DateTime  
    <MessageBodyMember()>  
    Public Property itinerary As Itinerary  
End Class  
  
<MessageContract()>  
Public Class GetAirfareResponse  
    <MessageBodyMember()>  
    Public Property airfare As Double  
    <MessageBodyMember()> Public Property currency As String  
End Class  
  
<DataContract()>  
Public Class Itinerary  
    <DataMember()> Public Property fromCity As String  
    <DataMember()> Public Property toCity As String  
End Class  

Para obter mais informações, consulte Como usar contratos de mensagem.

No exemplo anterior, a DataContractSerializer classe ainda é usada por padrão. A XmlSerializer classe também pode ser usada com contratos de mensagem. Para fazer isso, aplique o XmlSerializerFormatAttribute atributo à operação ou ao contrato e use tipos compatíveis com a XmlSerializer classe nos cabeçalhos e membros do corpo da mensagem.

Descrevendo mensagens usando fluxos

Outra maneira de descrever mensagens em operações é usar a Stream classe ou uma de suas classes derivadas em um contrato de operação ou como membro do corpo do contrato de mensagem (ele deve ser o único membro nesse caso). Para mensagens de entrada, o tipo deve ser Stream— você não pode usar classes derivadas.

Em vez de invocar o serializador, o WCF recupera dados de um fluxo e os coloca diretamente em uma mensagem de saída ou recupera dados de uma mensagem de entrada e os coloca diretamente em um fluxo. O exemplo a seguir mostra o uso de fluxos.

[OperationContract]  
public Stream DownloadFile(string fileName);  
<OperationContract()>  
Function DownloadFile(fileName As String) As String  

Não é possível combinar dados que não são de fluxo e Stream em um único corpo de mensagem. Use um contrato de mensagem para colocar os dados extras em cabeçalhos de mensagem. O exemplo a seguir mostra o uso incorreto de fluxos ao definir o contrato de operação.

//Incorrect:  
// [OperationContract]  
// public void UploadFile (string fileName, Stream fileData);  
'Incorrect:  
    '<OperationContract()>  
    Public Sub UploadFile(fileName As String, fileData As StreamingContext)  

O exemplo a seguir mostra o uso correto de fluxos ao definir um contrato de operação.

[OperationContract]  
public void UploadFile (UploadFileMessage message);  
//code omitted  
[MessageContract]  
public class UploadFileMessage  
{  
    [MessageHeader] public string fileName;  
    [MessageBodyMember] public Stream fileData;  
}  
<OperationContract()>  
Public Sub UploadFile(fileName As String, fileData As StreamingContext)  
'Code Omitted  
<MessageContract()>  
Public Class UploadFileMessage  
   <MessageHeader()>  
    Public Property fileName As String  
    <MessageBodyMember()>  
    Public Property fileData As Stream  
End Class  

Para obter mais informações, consulte Dados Grandes e Streaming.

Usando a classe de mensagem

Para ter controle programático completo sobre mensagens enviadas ou recebidas, você pode usar a Message classe diretamente, conforme mostrado no código de exemplo a seguir.

[OperationContract]  
public void LogMessage(Message m);  
<OperationContract()>  
Sub LogMessage(m As Message)  

Este é um cenário avançado, que é descrito em detalhes no Uso da Classe de Mensagem.

Descrever mensagens de falha

Além das mensagens descritas pelo valor retornado e parâmetros de saída ou referência, qualquer operação que não seja unidirecional pode retornar pelo menos duas mensagens possíveis: sua mensagem de resposta normal e uma mensagem de falha. Considere o contrato de operação a seguir.

[OperationContract]  
float GetAirfare(string fromCity, string toCity, DateTime date);  
<OperationContract()>  
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)  

Essa operação pode retornar uma mensagem normal que contém um float número ou uma mensagem de falha que contém um código de falha e uma descrição. Para isso, lance um FaultException na implementação do serviço.

Você pode especificar outras possíveis mensagens de falha usando o FaultContractAttribute atributo. As falhas adicionais devem ser serializáveis usando o DataContractSerializer, conforme mostrado no código de exemplo a seguir.

[OperationContract]  
[FaultContract(typeof(ItineraryNotAvailableFault))]  
float GetAirfare(string fromCity, string toCity, DateTime date);  
  
//code omitted  
  
[DataContract]  
public class ItineraryNotAvailableFault  
{  
    [DataMember]  
    public bool IsAlternativeDateAvailable;  
  
    [DataMember]  
    public DateTime alternativeSuggestedDate;  
}  
<OperationContract()>  
<FaultContract(GetType(ItineraryNotAvailableFault))>  
Function GetAirfare(fromCity As String, toCity As String, date as DateTime) As Double  
  
'Code Omitted  
<DataContract()>  
Public Class  
  <DataMember()>  
  Public Property IsAlternativeDateAvailable As Boolean  
  <DataMember()>  
  Public Property alternativeSuggestedDate As DateTime  
End Class  

Essas falhas adicionais podem ser geradas lançando um FaultException<TDetail> do tipo de contrato de dados apropriado. Para obter mais informações, consulte Como lidar com exceções e falhas.

Você não pode usar a XmlSerializer classe para descrever falhas. O XmlSerializerFormatAttribute não tem efeito nos contratos de falha.

Usando tipos derivados

Talvez você queira usar um tipo base em uma operação ou um contrato de mensagem e, em seguida, usar um tipo derivado ao invocar a operação. Nesse caso, você deve usar o ServiceKnownTypeAttribute atributo ou algum mecanismo alternativo para permitir o uso de tipos derivados. Considere a operação a seguir.

[OperationContract]  
public bool IsLibraryItemAvailable(LibraryItem item);  
<OperationContract()>  
    Function IsLibraryItemAvailable(item As LibraryItem) As Boolean  

Suponha que dois tipos Book e Magazine, derivam de LibraryItem. Para usar esses tipos na IsLibraryItemAvailable operação, você pode alterar a operação da seguinte maneira:

[OperationContract]

[ServiceKnownType(typeof(Book))]

[ServiceKnownType(typeof(Magazine))]

public bool IsLibraryItemAvailable(LibraryItem item);

Como alternativa, você pode usar o KnownTypeAttribute atributo quando o padrão DataContractSerializer estiver em uso, conforme mostrado no código de exemplo a seguir.

[OperationContract]  
public bool IsLibraryItemAvailable(LibraryItem item);  
  
// code omitted
  
[DataContract]  
[KnownType(typeof(Book))]  
[KnownType(typeof(Magazine))]  
public class LibraryItem  
{  
    //code omitted  
}  
<OperationContract()>  
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean  
  
'Code Omitted  
<DataContract()>  
<KnownType(GetType(Book))>  
<KnownType(GetType(Magazine))>  
Public Class LibraryItem  
  'Code Omitted  
End Class  

Você pode usar o XmlIncludeAttribute atributo ao usar o XmlSerializer.

Você pode aplicar o ServiceKnownTypeAttribute atributo a uma operação ou a todo o serviço. Ele aceita um tipo ou o nome do método a ser chamado para obter uma lista de tipos conhecidos, assim como o KnownTypeAttribute atributo. Para obter mais informações, consulte Tipos conhecidos do contrato de dados.

Especificando o uso e o estilo

Ao descrever os serviços usando a Linguagem de Descrição dos Serviços Web (WSDL), os dois estilos comumente usados são RPC (chamada de documento e procedimento remoto). No estilo Documento, todo o corpo da mensagem é descrito usando o esquema e o WSDL descreve as várias partes do corpo da mensagem referindo-se a elementos dentro desse esquema. No estilo RPC, o WSDL refere-se a um tipo de esquema para cada parte da mensagem em vez de um elemento. Em alguns casos, você precisa selecionar manualmente um desses estilos. Você pode fazer isso aplicando o DataContractFormatAttribute atributo e definindo a Style propriedade (quando estiver DataContractSerializer em uso) ou definindo Style no XmlSerializerFormatAttribute atributo (ao usar o XmlSerializer).

Além disso, o XmlSerializer oferece suporte para duas formas de XML serializado: Literal e Encoded. Literal é a forma mais comumente aceita e é o único formato que os DataContractSerializer suportam. Encoded é um formulário herdado descrito na seção 5 da especificação SOAP e não é recomendado para novos serviços. Para alternar para o modo Encoded, defina a propriedade Use no atributo XmlSerializerFormatAttribute como Encoded.

Na maioria das vezes, você não deve alterar as configurações padrão para as propriedades Style e Use.

Controlando o processo de serialização

Você pode fazer várias coisas para personalizar a maneira como os dados são serializados.

Alterando as configurações de serialização do servidor

Quando o padrão DataContractSerializer está em uso, você pode controlar alguns aspectos do processo de serialização no serviço aplicando o ServiceBehaviorAttribute atributo ao serviço. Especificamente, você pode usar a MaxItemsInObjectGraph propriedade para definir a cota que limita o número máximo de objetos que o DataContractSerializer desserializa. Você pode usar a propriedade IgnoreExtensionDataObject para desativar o recurso de versão de ida e volta. Para obter mais informações sobre cotas, consulte Considerações de segurança para dados. Para saber mais sobre a viagem de ida e volta, confira Contratos de dados compatíveis com versões posteriores.

[ServiceBehavior(MaxItemsInObjectGraph=100000)]  
public class MyDataService:IDataService  
{  
    public DataPoint[] GetData()  
    {  
       // Implementation omitted  
    }  
}  
<ServiceBehavior(MaxItemsInObjectGraph:=100000)>  
Public Class MyDataService Implements IDataService  
  
    Function GetData() As DataPoint()  
         ‘ Implementation omitted  
    End Function  
End Interface  

Comportamentos de serialização

Dois comportamentos estão disponíveis no WCF, DataContractSerializerOperationBehavior e XmlSerializerOperationBehavior, que são integrados automaticamente dependendo de qual serializador está em uso para uma operação específica. Como esses comportamentos são aplicados automaticamente, normalmente você não precisa estar ciente deles.

No entanto, o DataContractSerializerOperationBehavior tem as propriedades MaxItemsInObjectGraph, IgnoreExtensionDataObject e DataContractSurrogate que você pode usar para personalizar o processo de serialização. As duas primeiras propriedades têm o mesmo significado discutido na seção anterior. Você pode usar a DataContractSurrogate propriedade para habilitar substitutos de contrato de dados, que são um mecanismo poderoso para personalizar e estender o processo de serialização. Para obter mais informações, consulte Substitutos do Contrato de Dados.

Você pode usar o DataContractSerializerOperationBehavior para personalizar a serialização tanto do cliente quanto do servidor. O exemplo a seguir mostra como aumentar a MaxItemsInObjectGraph cota no cliente.

ChannelFactory<IDataService> factory = new ChannelFactory<IDataService>(binding, address);  
foreach (OperationDescription op in factory.Endpoint.Contract.Operations)  
{  
    DataContractSerializerOperationBehavior dataContractBehavior =  
                op.Behaviors.Find<DataContractSerializerOperationBehavior>()  
                as DataContractSerializerOperationBehavior;  
    if (dataContractBehavior != null)  
    {  
        dataContractBehavior.MaxItemsInObjectGraph = 100000;  
    }  
}  
IDataService client = factory.CreateChannel();  
Dim factory As ChannelFactory(Of IDataService) = New ChannelFactory(Of IDataService)(binding, address)  
For Each op As OperationDescription In factory.Endpoint.Contract.Operations  
        Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()  
        If dataContractBehavior IsNot Nothing Then  
            dataContractBehavior.MaxItemsInObjectGraph = 100000  
        End If  
     Next  
    Dim client As IDataService = factory.CreateChannel  

A seguir está o código equivalente no serviço, no caso autogerenciado:

ServiceHost serviceHost = new ServiceHost(typeof(IDataService))  
foreach (ServiceEndpoint ep in serviceHost.Description.Endpoints)  
{  
foreach (OperationDescription op in ep.Contract.Operations)  
{  
        DataContractSerializerOperationBehavior dataContractBehavior =  
           op.Behaviors.Find<DataContractSerializerOperationBehavior>()  
                as DataContractSerializerOperationBehavior;  
        if (dataContractBehavior != null)  
        {  
            dataContractBehavior.MaxItemsInObjectGraph = 100000;  
        }  
}  
}  
serviceHost.Open();  
Dim serviceHost As ServiceHost = New ServiceHost(GetType(IDataService))  
        For Each ep As ServiceEndpoint In serviceHost.Description.Endpoints  
            For Each op As OperationDescription In ep.Contract.Operations  
                Dim dataContractBehavior As DataContractSerializerOperationBehavior = op.Behaviors.Find(Of DataContractSerializerOperationBehavior)()  
  
                If dataContractBehavior IsNot Nothing Then  
                    dataContractBehavior.MaxItemsInObjectGraph = 100000  
                End If  
            Next  
        Next  
        serviceHost.Open()  

No caso da hospedagem na Web, é necessário criar uma classe derivada ServiceHost e usar um factory de host de serviço para conectá-la.

Controle das configurações de serialização na configuração

O MaxItemsInObjectGraph e IgnoreExtensionDataObject podem ser controlados por meio da configuração usando o ponto de extremidade dataContractSerializer ou o comportamento do serviço, conforme mostrado no exemplo a seguir.

<configuration>  
    <system.serviceModel>  
        <behaviors>  
            <endpointBehaviors>  
                <behavior name="LargeQuotaBehavior">  
                    <dataContractSerializer  
                      maxItemsInObjectGraph="100000" />  
                </behavior>  
            </endpointBehaviors>  
        </behaviors>  
        <client>  
            <endpoint address="http://example.com/myservice"  
                  behaviorConfiguration="LargeQuotaBehavior"  
                binding="basicHttpBinding" bindingConfiguration=""
                            contract="IDataService"  
                name="" />  
        </client>  
    </system.serviceModel>  
</configuration>  

Serialização de tipo compartilhado, preservação do grafo de objeto e serializadores personalizados

O DataContractSerializer serializa usando nomes de contrato de dados e não nomes de tipo .NET. Isso é consistente com os princípios de arquitetura orientados ao serviço e permite um grande grau de flexibilidade– os tipos .NET podem mudar sem afetar o contrato de transmissão. Em casos raros, talvez você queira serializar nomes de tipo .NET reais, introduzindo assim um acoplamento apertado entre o cliente e o servidor, semelhante à tecnologia de comunicação remota do .NET Framework. Essa não é uma prática recomendada, exceto em casos raros que geralmente ocorrem ao migrar para o WCF do .NET Framework remoto. Nesse caso, você deve usar a NetDataContractSerializer classe em vez da DataContractSerializer classe.

O DataContractSerializer normalmente serializa grafos de objeto como árvores de objetos. Ou seja, se o mesmo objeto for referenciado mais de uma vez, ele será serializado mais de uma vez. Por exemplo, considere uma PurchaseOrder instância que tenha dois campos do tipo Endereço chamado billTo e shipTo. Se ambos os campos estiverem definidos como a mesma instância de Endereço, haverá duas instâncias de Endereço idênticas após a serialização e a desserialização. Isso é feito porque não há uma maneira interoperável padrão de representar grafos de objeto em XML (exceto pelo padrão codificado SOAP herdado disponível no XmlSerializer, conforme descrito na seção anterior em Style e Use). Serializar grafos de objetos como árvores tem certas desvantagens, por exemplo, grafos com referências circulares não podem ser serializados. Ocasionalmente, é necessário alternar para a serialização real do grafo de objetos, mesmo que não seja interoperável. Isso pode ser feito usando o DataContractSerializer construído com o preserveObjectReferences parâmetro definido como true.

Ocasionalmente, os serializadores internos não são suficientes para o seu cenário. Na maioria das vezes, você ainda pode usar a abstração XmlObjectSerializer da qual derivam tanto a DataContractSerializer quanto a NetDataContractSerializer.

Os três casos anteriores (preservação de tipo .NET, preservação de grafo de objetos e serialização completamente personalizada e baseada em XmlObjectSerializer) exigem que um serializador personalizado seja conectado. Para fazer isso, execute as seguintes etapas:

  1. Escreva seu próprio comportamento derivando do DataContractSerializerOperationBehavior.

  2. Substitua os dois métodos CreateSerializer para retornar seu próprio serializador (seja o NetDataContractSerializer, o DataContractSerializer com preserveObjectReferences definido como true ou seu próprio XmlObjectSerializer personalizado).

  3. Antes de abrir o host de serviço ou criar um canal cliente, remova o comportamento existente DataContractSerializerOperationBehavior e conecte a classe derivada personalizada que você criou nas etapas anteriores.

Para obter mais informações sobre conceitos avançados de serialização, consulte Serialização e Desserialização.

Consulte também