Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Windows Communication Foundation (WCF) se puede considerar como una infraestructura de mensajería. Las operaciones de servicio pueden recibir mensajes, procesarlos y enviarlos. Los mensajes se describen mediante contratos de operación. Por ejemplo, considere el siguiente 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
Aquí, la GetAirfare
operación acepta un mensaje con información sobre fromCity
y toCity
y, a continuación, devuelve un mensaje que contiene un número.
En este tema se explican las distintas formas en que un contrato de operación puede describir los mensajes.
Descripción de mensajes mediante parámetros
La manera más sencilla de describir un mensaje es usar una lista de parámetros y el valor devuelto. En el ejemplo anterior, los fromCity
parámetros de cadena y toCity
se usaron para describir el mensaje de solicitud y el valor devuelto float se usó para describir el mensaje de respuesta. Si el valor devuelto por sí solo no es suficiente para describir un mensaje de respuesta, se pueden usar parámetros out. Por ejemplo, la siguiente operación tiene fromCity
y toCity
en su mensaje de solicitud, y un número junto con una moneda en su mensaje de respuesta:
[OperationContract]
float GetAirfare(string fromCity, string toCity, out string currency);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String) As Double
Además, puede usar parámetros de referencia para crear una parte del parámetro tanto de la solicitud como del mensaje de respuesta. Los parámetros deben ser de tipos que se pueden serializar (convertidos en XML). De forma predeterminada, WCF usa un componente denominado clase DataContractSerializer para realizar esta conversión. Se admiten la mayoría de los tipos primitivos (como int
, string
, float
y DateTime
). Los tipos definidos por el usuario normalmente deben tener un contrato de datos. Para obtener más información, consulte Uso de contratos de datos.
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
En ocasiones, DataContractSerializer
no es adecuado para serializar sus tipos. WCF admite un motor de serialización alternativo, el XmlSerializer, que también puede usar para serializar parámetros. XmlSerializer permite usar más control sobre el XML resultante mediante atributos como XmlAttributeAttribute
. Para cambiar al uso XmlSerializer de para una operación determinada o para todo el servicio, aplique el XmlSerializerFormatAttribute atributo a una operación o a un servicio. Por ejemplo:
[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 obtener más información, vea Usar la clase XmlSerializer. Recuerde que no se recomienda cambiar manualmente a como XmlSerializer se muestra aquí, a menos que tenga razones específicas para hacerlo, como se detalla en ese tema.
Para aislar los nombres de parámetro de .NET de los nombres de contrato, puede usar el MessageParameterAttribute atributo y usar la Name
propiedad para establecer el nombre del contrato. Por ejemplo, el siguiente contrato de operación es equivalente al primer ejemplo de este tema.
[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
Descripción de mensajes vacíos
Un mensaje de solicitud vacío se puede describir sin parámetros de entrada o referencia. Por ejemplo, en C#:
[OperationContract]
public int GetCurrentTemperature();
Por ejemplo, en Visual Basic:
<OperationContract()>
Function GetCurrentTemperature() as Integer
Un mensaje de respuesta vacío se puede describir teniendo un void
tipo de valor devuelto y sin parámetros de salida o referencia. Por ejemplo, en:
[OperationContract]
public void SetTemperature(int temperature);
<OperationContract()>
Sub SetTemperature(temperature As Integer)
Esto es diferente de una operación unidireccional, como:
[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);
<OperationContract(IsOneWay:=True)>
Sub SetLightbulbStatus(isOne As Boolean)
La SetTemperatureStatus
operación devuelve un mensaje vacío. Puede devolver un error en su lugar si hay un problema al procesar el mensaje de entrada. La SetLightbulbStatus
operación no devuelve nada. No hay ninguna manera de comunicar una condición de error desde esta operación.
Descripción de mensajes mediante contratos de mensajes
Es posible que desee usar un solo tipo para representar todo el mensaje. Aunque es posible usar un contrato de datos para este propósito, la manera recomendada de hacerlo es usar un contrato de mensaje, lo que evita niveles innecesarios de ajuste en el XML resultante. Además, los contratos de mensajes permiten ejercer más control sobre los mensajes resultantes. Por ejemplo, puede decidir qué partes de información deben estar en el cuerpo del mensaje y cuáles deben estar en los encabezados del mensaje. En el ejemplo siguiente se muestra el uso de contratos de mensajes.
[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 obtener más información, consulte Uso de contratos de mensajes.
En el ejemplo anterior, la DataContractSerializer clase todavía se usa de forma predeterminada. La XmlSerializer clase también se puede usar con contratos de mensajes. Para ello, aplique el atributo XmlSerializerFormatAttribute a la operación o al contrato y utilice tipos compatibles con la clase XmlSerializer en los encabezados del mensaje y miembros del cuerpo.
Descripción de mensajes mediante secuencias
Otra manera de describir los mensajes en operaciones es usar la Stream clase o una de sus clases derivadas en un contrato de operación o como miembro del cuerpo del contrato de mensaje (debe ser el único miembro en este caso). Para los mensajes entrantes, el tipo debe ser Stream
: no se pueden usar clases derivadas.
En lugar de invocar el serializador, WCF recupera datos de una secuencia y los coloca directamente en un mensaje saliente, o recupera datos de un mensaje entrante y los coloca directamente en una secuencia. El siguiente ejemplo muestra el uso de flujos.
[OperationContract]
public Stream DownloadFile(string fileName);
<OperationContract()>
Function DownloadFile(fileName As String) As String
No puede combinar datos que no sean de secuencia y datos de Stream
en un cuerpo de mensaje único. Use un contrato de mensaje para colocar los datos adicionales en los encabezados de mensaje. En el ejemplo siguiente se muestra el uso incorrecto de secuencias al definir el contrato de operación.
//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);
'Incorrect:
'<OperationContract()>
Public Sub UploadFile(fileName As String, fileData As StreamingContext)
En el ejemplo siguiente se muestra el uso correcto de secuencias al definir un contrato de operación.
[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 obtener más información, consulte Datos grandes y streaming.
Uso de la clase message
Para tener un control mediante programación completo sobre los mensajes enviados o recibidos, puede usar la Message clase directamente, como se muestra en el código de ejemplo siguiente.
[OperationContract]
public void LogMessage(Message m);
<OperationContract()>
Sub LogMessage(m As Message)
Se trata de un escenario avanzado, que se describe en detalle en Uso de la clase message.
Descripción de mensajes de error
Además de los mensajes descritos por el valor devuelto y los parámetros de salida o referencia, cualquier operación que no sea unidireccional puede devolver al menos dos mensajes posibles: su mensaje de respuesta normal y un mensaje de error. Tenga en cuenta el siguiente contrato de operación.
[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);
<OperationContract()>
Function GetAirfare(fromCity As String, toCity As String, date as DateTime)
Esta operación puede devolver un mensaje normal que contenga un float
número o un mensaje de error que contenga un código de error y una descripción. Puede lograr esto lanzando un FaultException en su implementación del servicio.
Puede especificar mensajes de error adicionales posibles mediante el FaultContractAttribute atributo . Los errores adicionales deben ser serializables mediante DataContractSerializer, como se muestra en el código de ejemplo siguiente.
[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
Estos errores adicionales se pueden generar lanzando un FaultException<TDetail> del tipo de contrato de datos adecuado. Para obtener más información, consulte Control de excepciones y errores.
No se puede usar la XmlSerializer clase para describir errores. El XmlSerializerFormatAttribute no tiene efecto en contratos de error.
Uso de tipos derivados
Es posible que desee usar un tipo base en una operación o un contrato de mensaje y, a continuación, usar un tipo derivado al invocar realmente la operación. En este caso, debe usar el ServiceKnownTypeAttribute atributo o algún mecanismo alternativo para permitir el uso de tipos derivados. Considere la siguiente operación.
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
<OperationContract()>
Function IsLibraryItemAvailable(item As LibraryItem) As Boolean
Supongamos que los tipos Book
y Magazine
derivan de LibraryItem
. Para usar estos tipos en la operación IsLibraryItemAvailable
, puede cambiar la operación de la siguiente manera:
[OperationContract]
[ServiceKnownType(typeof(Book))]
[ServiceKnownType(typeof(Magazine))]
public bool IsLibraryItemAvailable(LibraryItem item);
Como alternativa, puede usar el KnownTypeAttribute atributo cuando el valor predeterminado DataContractSerializer está en uso, como se muestra en el código de ejemplo siguiente.
[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
Puede usar el atributo XmlIncludeAttribute al usar el XmlSerializer.
Puede aplicar el ServiceKnownTypeAttribute atributo a una operación o a todo el servicio. Acepta un tipo o el nombre del método para llamar y obtener una lista de tipos conocidos, al igual que el atributo KnownTypeAttribute. Para obtener más información, vea Tipos conocidos del contrato de datos.
Especificar el uso y el estilo
Al describir los servicios mediante el lenguaje de descripción de servicios web (WSDL), los dos estilos usados habitualmente son document y llamada a procedimiento remoto (RPC). En el estilo Documento, todo el cuerpo del mensaje se describe mediante el esquema y el WSDL describe las distintas partes del cuerpo del mensaje haciendo referencia a elementos dentro de ese esquema. En el estilo RPC, el WSDL hace referencia a un tipo de esquema para cada parte de mensaje en lugar de a un elemento. En algunos casos, tiene que seleccionar manualmente uno de estos estilos. Puede hacerlo aplicando el DataContractFormatAttribute atributo y estableciendo la Style
propiedad (cuando DataContractSerializer está en uso) o estableciendo Style
en el XmlSerializerFormatAttribute atributo (cuando se usa XmlSerializer).
Además, XmlSerializer admite dos formas de XML serializado: Literal
y Encoded
. Literal
es la forma más comúnmente aceptada y la única forma que DataContractSerializer admite. Encoded
es un formulario heredado descrito en la sección 5 de la especificación SOAP y no se recomienda para los nuevos servicios. Para cambiar al Encoded
modo, establezca la propiedad Use
en el atributo XmlSerializerFormatAttribute a Encoded
.
En la mayoría de los casos, no debe cambiar la configuración predeterminada de las Style
propiedades y Use
.
Control del proceso de serialización
Puede hacer una serie de cosas para personalizar la forma en que se serializan los datos.
Cambio de la configuración de serialización del servidor
Cuando el valor predeterminado DataContractSerializer está en uso, puede controlar algunos aspectos del proceso de serialización en el servicio aplicando el ServiceBehaviorAttribute atributo al servicio. En concreto, puede usar la MaxItemsInObjectGraph
propiedad para establecer la cuota que limita el número máximo de objetos que deserializa DataContractSerializer . Puede usar la propiedad IgnoreExtensionDataObject
para desactivar la funcionalidad de versionado de ida y vuelta. Para obtener más información sobre las cuotas, consulte Consideraciones de seguridad para datos. Para obtener más información, consulte Contratos de datos compatibles con el reenvío.
[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
Comportamientos de serialización
Hay dos comportamientos disponibles en WCF, el DataContractSerializerOperationBehavior y el XmlSerializerOperationBehavior que se integran automáticamente dependiendo de qué serializador se esté usando en una operación determinada. Dado que estos comportamientos se aplican automáticamente, normalmente no es necesario tener en cuentalos.
Sin embargo, DataContractSerializerOperationBehavior
tiene las MaxItemsInObjectGraph
propiedades , IgnoreExtensionDataObject
y DataContractSurrogate
que puede usar para personalizar el proceso de serialización. Las dos primeras propiedades tienen el mismo significado que se describe en la sección anterior. Puede usar la DataContractSurrogate
propiedad para habilitar suplentes de contrato de datos, que son un mecanismo eficaz para personalizar y ampliar el proceso de serialización. Para obtener más información, consulte Representantes del contrato de datos.
Puede usar DataContractSerializerOperationBehavior
para personalizar la serialización de cliente y servidor. En el ejemplo siguiente se muestra cómo aumentar la MaxItemsInObjectGraph
cuota en el 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 continuación, se muestra el código equivalente en el servicio, en el caso de autoalojamiento:
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()
En el caso de hospedaje mediante web, debe crear una nueva clase derivada ServiceHost
y utilizar un generador de host de servicio para conectarla.
Controlar la configuración de serialización en la configuración
El MaxItemsInObjectGraph
y IgnoreExtensionDataObject
se pueden controlar a través de la configuración utilizando el comportamiento de servicio o del punto de enlace dataContractSerializer
, como se muestra en el ejemplo siguiente.
<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>
Serialización de tipos compartidos, conservación de gráficos de objetos y serializadores personalizados
DataContractSerializer serializa utilizando nombres de contrato de datos y no nombres de tipo .NET. Esto es coherente con los principios de arquitectura orientados a servicios y permite un gran grado de flexibilidad: los tipos de .NET pueden cambiar sin afectar al contrato de conexión. En raras ocasiones, es posible que desee serializar nombres de tipo .NET reales, con lo que se introduce un acoplamiento estricto entre el cliente y el servidor, similar a la tecnología de comunicación remota de .NET Framework. Este no es un procedimiento recomendado, excepto en raros casos que normalmente tienen lugar al migrar a WCF desde comunicaciones remotas de .NET Framework. En este caso, debe usar la NetDataContractSerializer clase en lugar de la DataContractSerializer clase .
Normalmente DataContractSerializer serializa gráficos de objetos como árboles de objetos. Es decir, si se hace referencia al mismo objeto más de una vez, se serializa más de una vez. Por ejemplo, considere una PurchaseOrder
instancia que tenga dos campos de tipo Dirección denominados billTo
y shipTo
. Si ambos campos están establecidos en la misma instancia de Dirección, hay dos instancias de Dirección idénticas después de la serialización y deserialización. Esto se hace porque no hay ninguna manera interoperable estándar de representar gráficos de objetos en XML (excepto el estándar codificado SOAP heredado disponible en XmlSerializer, como se describe en la sección anterior de Style
y Use
). La serialización de gráficos de objetos como árboles tiene ciertas desventajas, por ejemplo, los gráficos con referencias circulares no se pueden serializar. En ocasiones, es necesario recurrir a la serialización verdadera de gráficos de objetos, aunque no sea interoperable. Esto se puede hacer mediante el DataContractSerializer, construido con el parámetro preserveObjectReferences
establecido en true
.
En ocasiones, los serializadores integrados no son suficientes para su escenario. En la mayoría de los casos, todavía puede usar la XmlObjectSerializer abstracción de la cual tanto el DataContractSerializer como el NetDataContractSerializer se derivan.
Los tres casos anteriores (conservación de tipos de .NET, conservación de grafos de objetos y serialización completamente personalizada basada en XmlObjectSerializer
) requieren que se utilice un serializador personalizado. Para ello, realice los siguientes pasos:
Escriba su propio comportamiento que deriva del DataContractSerializerOperationBehavior.
Invalide los dos métodos
CreateSerializer
para devolver su propio serializador (NetDataContractSerializer, DataContractSerializer conpreserveObjectReferences
establecido entrue
o su propio XmlObjectSerializer personalizado).Antes de abrir el host de servicio o crear un canal de cliente, quite el comportamiento existente DataContractSerializerOperationBehavior y conecte la clase derivada personalizada que creó en los pasos anteriores.
Para obtener más información sobre los conceptos avanzados de serialización, consulte Serialización y deserialización.