Especificando a transferência de dados em contratos de serviços
O Windows Communication Foundation (WCF) pode ser considerado como uma infraestrutura de mensagens. As operações de serviço podem receber mensagens, processá-las e enviar-lhes mensagens. As mensagens são descritas usando contratos de operação. Por exemplo, considere o seguinte contrato.
[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 toCity
e, em seguida, retorna 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 de retorno. No exemplo anterior, os fromCity
parâmetros e toCity
string foram usados para descrever a mensagem de solicitação e o valor de retorno float foi usado para descrever a mensagem de resposta. Se o valor de retorno por si só não for suficiente para descrever uma mensagem de resposta, os parâmetros de saída podem ser usados. Por exemplo, a seguinte operação tem fromCity
e toCity
em sua mensagem de solicitação, e um número juntamente 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 da solicitação e da mensagem de resposta. Os parâmetros devem ser de tipos que podem ser serializados (convertidos em XML). Por padrão, o DataContractSerializer WCF usa um componente chamado classe para executar essa conversão. A maioria dos tipos primitivos (como int
, string
, float
e DateTime
.) são suportados. Os tipos definidos pelo usuário normalmente devem ter um contrato de dados. Para obter mais informações, consulte Usando 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. WCF suporta um mecanismo de serialização alternativo, o XmlSerializer, que você também pode usar para serializar parâmetros. O XmlSerializer permite que você use mais controle sobre o XML resultante usando atributos como o XmlAttributeAttribute
. Para alternar para usar o XmlSerializer para uma operação específica ou para todo o serviço, aplique o XmlSerializerFormatAttribute atributo a uma operação ou 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 Usando a classe XmlSerializer. Lembre-se de que mudar manualmente para o como mostrado aqui não é recomendado, XmlSerializer a menos que você tenha razões específicas para fazê-lo, conforme detalhado nesse tópico.
Para isolar nomes de parâmetros .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
Descrevendo mensagens vazias
Uma mensagem de solicitação vazia pode ser descrita por não ter 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 com um tipo de void
retorno e sem parâmetros 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 pode retornar uma falha se houver um problema no processamento da mensagem de entrada. A SetLightbulbStatus
operação não retorna nada. Não há nenhuma maneira de comunicar uma condição de falha a partir desta operação.
Descrevendo mensagens usando contratos de mensagem
Você pode querer 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 — isso evita níveis desnecessários de encapsulamento no XML resultante. Além disso, os contratos de mensagens permitem que você exerça mais controle sobre as mensagens resultantes. Por exemplo, você pode decidir quais informações devem estar no corpo da mensagem e quais devem estar nos cabeçalhos das mensagens. 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 Usando 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 da mensagem e nos membros do corpo.
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 um membro do corpo do contrato de mensagem (deve ser o único membro neste 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 Stream
e não transmitir dados em um único corpo de mensagem. Use um contrato de mensagem para colocar os dados extras nos cabeçalhos das mensagens. 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 Message
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 em Usando a classe de mensagem.
Descrevendo mensagens de falha
Além das mensagens descritas pelo valor de retorno 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 seguinte contrato de operação.
[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)
Esta 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. Você pode fazer isso lançando um FaultException em sua implementação de serviço.
Você pode especificar possíveis mensagens de falha adicionais 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 Manipulando exceções e falhas.
Não é possível usar a XmlSerializer classe para descrever falhas. O XmlSerializerFormatAttribute não tem efeito sobre os contratos de culpa.
Usando tipos derivados
Você pode querer usar um tipo base em uma operação ou um contrato de mensagem e, em seguida, usar um tipo derivado ao realmente 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 seguinte operação.
[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 alterá-la 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 de contrato de dados.
Especificando o uso e o estilo
Ao descrever serviços usando WSDL (Web Services Description Language), os dois estilos comumente usados são Document e RPC (chamada de 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 aos 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 o DataContractSerializer está em uso) ou definindo Style
o XmlSerializerFormatAttribute atributo (ao usar o XmlSerializer).
Além disso, o XmlSerializer suporta duas formas de XML serializado: Literal
e Encoded
. Literal
é a forma mais comummente aceite, e é a única forma de DataContractSerializer suportes. 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 Encoded
modo, defina a Use
XmlSerializerFormatAttribute propriedade no atributo como Encoded
.
Na maioria dos casos, você não deve alterar as configurações padrão para as Style
propriedades 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 aspetos 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 são DataContractSerializer desserializados. Você pode usar a IgnoreExtensionDataObject
propriedade para desativar o recurso de controle de versão de ida e volta. Para obter mais informações sobre cotas, consulte Considerações de segurança para dados. Para obter mais informações sobre round-tripping, consulte Forward-Compatible Data Contracts.
[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, o DataContractSerializerOperationBehavior e o XmlSerializerOperationBehavior que são automaticamente conectados dependendo de qual serializador está em uso para uma operação específica. Como esses comportamentos são aplicados automaticamente, você normalmente não precisa estar ciente deles.
No entanto, o DataContractSerializerOperationBehavior
tem o MaxItemsInObjectGraph
, IgnoreExtensionDataObject
e DataContractSurrogate
propriedades 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 propriedade para habilitar substitutos DataContractSurrogate
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 de contrato de dados.
Você pode usar o para personalizar a DataContractSerializerOperationBehavior
serialização de cliente e servidor. O exemplo a seguir mostra como aumentar a cota MaxItemsInObjectGraph
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
O seguinte é o código equivalente no serviço, no caso auto-hospedado:
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 hospedado na Web, você deve criar uma nova ServiceHost
classe derivada e usar uma fábrica de host de serviço para conectá-la.
Controlando as definições de serialização na configuração
O MaxItemsInObjectGraph
e IgnoreExtensionDataObject
pode ser controlado por meio da configuração usando o comportamento de dataContractSerializer
ponto de extremidade ou 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 de gráfico de objeto e serializadores personalizados
Os DataContractSerializer serializa usando nomes de contrato de dados e não nomes de tipo .NET. Isso é consistente com os princípios da arquitetura orientada a serviços e permite um grande grau de flexibilidade — os tipos .NET podem mudar sem afetar o contrato de fio. Em casos raros, você pode querer serializar nomes de tipo .NET reais, introduzindo assim um acoplamento estreito 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 a partir da comunicação remota do .NET Framework. Nesse caso, você deve usar a NetDataContractSerializer classe em vez da DataContractSerializer classe.
O DataContractSerializer normalmente serializa gráficos de objeto como árvores de objetos. Ou seja, se o mesmo objeto é referido mais de uma vez, ele é 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 para a mesma instância Address, haverá duas instâncias de Address idênticas após a serialização e a desserialização. Isso é feito porque não há nenhuma maneira interoperável padrão para representar gráficos de objeto em XML (exceto para o padrão codificado SOAP herdado disponível no XmlSerializer, conforme descrito na seção anterior em Style
e Use
). A serialização de gráficos de objetos como árvores tem certas desvantagens, por exemplo, gráficos com referências circulares não podem ser serializados. Ocasionalmente, é necessário alternar para a serialização de gráficos de objetos verdadeiros, mesmo que ela 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 dos casos, você ainda pode usar a XmlObjectSerializer abstração da qual tanto o quanto o DataContractSerializerNetDataContractSerializer derivam.
Os três casos anteriores (preservação de tipo .NET, preservação de gráfico de objeto e serialização completamente personalizada XmlObjectSerializer
) exigem que um serializador personalizado seja conectado. Para tal, execute os seguintes passos:
Escreva seu próprio comportamento derivado do DataContractSerializerOperationBehavior.
Substitua os dois
CreateSerializer
métodos para retornar seu próprio serializador (o NetDataContractSerializer, o DataContractSerializer compreserveObjectReferences
definido comotrue
, ou seu próprio personalizado XmlObjectSerializer).Antes de abrir o host de serviço ou criar um canal de 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.