Especificación de transferencia de datos en contratos de servicio

Windows Communication Foundation (WCF) se puede ver como una infraestructura de mensajería. Las operaciones de servicio pueden recibir mensajes, procesarlos y enviarles mensajes. Los mensajes se describen mediante contratos de operaciones. Por ejemplo, considere el siguiente contrato:

[ServiceContract]
public interface IAirfareQuoteService
{
    [OperationContract]
    float GetAirfare(string fromCity, string toCity);
}

Aquí, la operación GetAirfare acepta un mensaje con información sobre fromCity y toCityy, a continuación, devuelve un mensaje que contiene un número.

En este tema se explican las varias maneras en las que un contrato de operación puede describir los mensajes.

Descripción de mensajes mediante parámetros

La manera más simple de describir un mensaje consiste en utilizar una lista de parámetros y el valor devuelto. En el ejemplo anterior, los parámetros de cadena fromCity y toCity se utilizaron para describir el mensaje de solicitud y el valor devuelto flotante se utilizó para describir el mensaje de respuesta. Si el valor devuelto por sí solo no es suficiente para describir un mensaje de respuesta, se pueden utilizar 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);

Además, puede utilizar parámetros de referencia para hacer que un parámetro forme parte tanto del mensaje de solicitud como del de respuesta. Los parámetros deben ser de tipos que se pueden serializar (convertidos a XML). De manera predeterminada, WCF utiliza un componente llamada la clase DataContractSerializer para realizar esta conversión. Se admite la mayoría de datos primitivos (como int, string, float, y DateTime). Los tipos definidos por el usuario deben tener normalmente un contrato de datos. Para obtener más información, consulte Utilización 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;
}

De vez en cuando, el DataContractSerializer no es adecuado para serializar sus tipos. WCF admite un motor de serialización alternativo, XmlSerializer, que también puede utilizar para serializar parámetros. XmlSerializer le permite utilizar más control sobre el XML resultante mediante atributos como el XmlAttributeAttribute. Para pasar a utilizar XmlSerializer para una operación determinada o para el servicio completo, aplique el atributo XmlSerializerFormatAttribute a una operación o 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;
}

Para obtener más información, consulte Utilización de la clase XmlSerializer. Recuerde que intercambiar manualmente a XmlSerializer, tal y como se muestra aquí, no se recomienda a menos que tenga razones concretas para hacerlo tal y como se detalla en ese tema.

Para aislar los nombres de parámetro .NET de los nombres de contrato, puede utilizar el atributo MessageParameterAttribute y utilizar la propiedad Name para establecer el nombre de contrato. Por ejemplo, el contrato de operación siguiente es equivalente al primer ejemplo de este tema.

[OperationContract]
public float GetAirfare(
    [MessageParameter(Name=”fromCity”)] string originCity,
    [MessageParameter(Name=”toCity”)] string destinationCity);

Descripción de mensajes vacíos

Un mensaje de solicitud vacío se puede describir no teniendo ninguna entrada ni parámetro de referencia. Por ejemplo:

[OperationContract]

public int GetCurrentTemperature();

Un mensaje de respuesta vacío se puede describir teniendo un tipo de valor devuelto void y ningún resultado ni parámetro de referencia. Por ejemplo:

[OperationContract]
public void SetTemperature(int temperature);

Esto es diferente de una operación unidireccional, como:

[OperationContract(IsOneWay=true)]
public void SetLightbulbStatus(bool isOn);

La operación SetTemperatureStatus devuelve un mensaje vacío. Puede devolver en su lugar un error si hay un problema al procesar un mensaje de entrada. La operación SetLightbulbStatus no devuelve nada. No hay ninguna manera de comunicar una condición de error de esta operación.

Descripción de mensajes mediante el uso de contratos de mensaje

Puede desear utilizar un tipo único para representar el mensaje completo. Aunque es posible de utilizar un contrato de datos para este propósito, la manera recomendada para ello consiste en utilizar un contrato de mensaje; esto evita niveles innecesarios de ajuste en el XML resultante. Además, los contratos de mensaje le permiten ejercer más control sobre los mensajes resultantes. Por ejemplo, puede decidir qué partes de información deberían estar en el cuerpo del mensaje y cuáles deberían estar en los encabezados del mensaje. En el siguiente ejemplo se muestra el uso de contratos de mensaje.

[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;
}

Para obtener más información, consulte Usar contratos de mensaje.

En el ejemplo anterior, la clase DataContractSerializer se sigue utilizando de forma predeterminada. La clase XmlSerializer también se puede usar con contratos de mensaje. 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 el uso de secuencias

Otra manera de describir mensajes en operaciones consiste en utilizar la clase Stream o una de sus clases derivadas en un contrato de operación o como un miembro de cuerpo del contrato de mensaje (debe ser el único miembro en este caso). Para los mensajes entrantes, el tipo debe ser Stream; no puede utilizar clases derivadas.

En lugar de invocar el serializador, WCF recupera los datos a partir de una secuencia y los coloca directamente en un mensaje saliente o recupera datos a partir de un mensaje entrante y los coloca directamente en una secuencia. En el siguiente ejemplo se muestra la forma de utilizar secuencias.

[OperationContract]
public Stream DownloadFile(string fileName);

No puede combinar datos que no sean de secuencia y datos de Stream en un cuerpo de mensaje único. Utilice un contrato de mensaje para colocar los datos adicionales en encabezados de mensaje. El ejemplo siguiente muestra el uso incorrecto de secuencias al definir el contrato de operación.

//Incorrect:
// [OperationContract]
// public void UploadFile (string fileName, Stream fileData);

El ejemplo siguiente 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;
}

Para obtener más información, consulte Datos de gran tamaño y secuencias.

Utilización de la clase de mensajes

Para tener control completo de programación sobre los mensajes enviados o recibidos, puede utilizar directamente la clase Message, tal y como se muestra en el siguiente código de ejemplo.

[OperationContract]
public void LogMessage(Message m);

Éste es un escenario avanzado, que se describe en detalle en Utilización de la clase de mensajes.

Descripción de mensajes de error

Además de los mensajes que son descritos por el valor devuelto y los parámetros de referencia o salida, cualquier operación que no sea unidireccional puede devolver al menos dos posibles mensajes: su mensaje de respuesta normal y un mensaje de error. Considere el contrato de operación siguiente.

[OperationContract]
float GetAirfare(string fromCity, string toCity, DateTime date);

Esta operación puede devolver un mensaje normal que contenga un número float o un mensaje de error que contenga un código de error y una descripción. Puede lograr esto generando una FaultException en su implementación del servicio.

Puede especificar posibles mensajes de error adicionales utilizando el atributo FaultContractAttribute. Los errores adicionales se han de poder serializar mediante el DataContractSerializer, tal y 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;
}

Estos errores adicionales se pueden generar mediante la generación de una FaultException del tipo de contrato de datos adecuado. Para obtener más información, consulte Administración de excepciones y errores.

No puede utilizar la clase XmlSerializer para describir los errores. El XmlSerializerFormatAttribute no tiene efecto en contratos de error.

Uso de tipos derivados

Puede desear utilizar un tipo base en una operación o un contrato de mensaje y, a continuación, utilizar un tipo derivado al invocar verdaderamente la operación. En este caso, debe utilizar el atributo ServiceKnownTypeAttribute o algún mecanismo alternativo para permitir el uso de tipos derivados. Considere la siguiente operación.

[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);

Suponga que dos tipos, Book y Magazine, derivan de LibraryItem. Para utilizar estos tipos en la operación IsLibraryItemAvailable, puede cambiar la operación tal y como sigue:

[OperationContract]

[ServiceKnownType(typeof(Book))]

[ServiceKnownType(typeof(Magazine))]

public bool IsLibraryItemAvailable(LibraryItem item);

Alternativamente, puede utilizar el atributo KnownTypeAttribute cuando el DataContractSerializer predeterminado se esté utilizando, tal y 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…
}

Puede utilizar el atributo XmlIncludeAttribute al usar el XmlSerializer.

Puede aplicar el atributo ServiceKnownTypeAttribute a una operación o al servicio completo. Acepta un tipo o el nombre del método para llamar para obtener una lista de tipos conocidos, como el atributo KnownTypeAttribute. Para obtener más información, consulte Tipos conocidos de contratos de datos.

Especificación del uso y estilo

Al describir servicios mediante el lenguaje de descripción de servicios web (WSDL), los dos estilos utilizados comúnmente son Documento y llamada a procedimiento remoto (RPC). En el estilo Documento, el cuerpo del mensaje completo se describe utilizando el esquema, y el WSDL describe las diversas 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 del mensaje en lugar de a un elemento. En algunos casos, tiene que seleccionar manualmente uno de estos estilos. Puede hacer esto aplicando el atributo DataContractFormatAttribute y estableciendo la propiedad Style (cuando se esté usando DataContractSerializer), o estableciendo el Style en el atributo XmlSerializerFormatAttribute (al usar el XmlSerializer).

Por otra parte, XmlSerializer admite dos formas de XML serializado: Literal y Encoded. Literal es la forma más comúnmente aceptada y la única que DataContractSerializer admite. Encoded es una forma heredada descrita en la sección 5 de la especificación SOAP y no se recomienda para nuevos servicios. Para cambiar al modo Encoded, establezca la propiedad Use en el atributo XmlSerializerFormatAttribute en Encoded.

En la mayoría de los casos, no debería cambiar la configuración predeterminada para el Style ni las propiedades Use.

Control del proceso de serialización

Puede hacer varias cosas para personalizar la manera en la que se serializan los datos.

Cambio de los ajustes de serialización del servidor

Cuando el DataContractSerializer predeterminado se está utilizando, puede controlar algunos aspectos del proceso de serialización en el servicio aplicando el atributo ServiceBehaviorAttribute al servicio. Específicamente, puede utilizar la propiedad MaxItemsInObjectGraph para establecer la cuota que limita el número máximo de objetos que DataContractSerializer deserializa. Puede utilizar la propiedad IgnoreExtensionDataObject para desactivar la función de versión de recorridos de ida y vuelta. Para obtener más información acerca de cuotas, vea Consideraciones de seguridad para datos. Para obtener más información acerca de recorridos de ida y vuelta, vea Contratos de datos compatibles con el reenvío.

[ServiceContract]
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public interface IDataService
{
    [OperationContract] DataPoint[] GetData();
}

Comportamientos de serialización

Hay disponibles dos comportamientos en WCF, DataContractSerializerOperationBehavior y XmlSerializerOperationBehavior que se conectan automáticamente en función de qué serializador se utilice para una operación determinada. Dado que se aplican automáticamente estos comportamientos, normalmente no tiene que estar al tanto sobre ellos.

Sin embargo, DataContractSerializerOperationBehavior tiene MaxItemsInObjectGraph, IgnoreExtensionDataObject, y las propiedades DataContractSurrogate que puede utilizar para personalizar el proceso de serialización. Las primeras dos propiedades tienen el mismo significado que se mencionó en la sección anterior. Puede utilizar la propiedad DataContractSurrogate para habilitar suplentes del contrato de datos, que son un mecanismo eficaz para personalizar y extender el proceso de serialización. Para obtener más información, consulte Suplentes de contratos de datos.

Puede utilizar el DataContractSerializerOperationBehavior para personalizar la serialización de cliente y servidor. El ejemplo siguiente muestra cómo aumentar la cuota MaxItemsInObjectGraph 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();

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();

En el caso de alojamiento mediante Web, debe crear una nueva clase derivada ServiceHost y utilizar un generador de host de servicio para conectarla.

Control de los ajustes de serialización en la configuración

El MaxItemsInObjectGraph e IgnoreExtensionDataObject se pueden controlar a través de la configuración utilizando el extremo dataContractSerializer o comportamiento de servicio, tal y 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 tipo compartido, preservación de gráfico de objeto y serializadores personalizados

DataContractSerializer serializa utilizando nombres de contrato de datos y no nombres de tipo .NET. Esto es coherente con los principios de la arquitectura orientada a servicios y permite un gran grado de flexibilidad; los tipos .NET pueden cambiar sin afectar al contrato de conexión. En algún caso aislado, puede que desee serializar nombres de tipo .NET reales, introduciendo, de este modo, un acoplamiento robusto entre el cliente y el servidor, similar a la tecnología remota de .NET Framework. Éste 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 utilizar la clase NetDataContractSerializer, en lugar de la clase DataContractSerializer.

El DataContractSerializer serializa normalmente gráficos de objetos como árboles de objeto. 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 instancia PurchaseOrder que tiene dos campos de tipo Dirección llamados 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 para el estándar codificado SOAP heredado disponible en el XmlSerializer, tal y como se describe en la sección anterior en Style y Use). Serializar gráficos de objetos como árboles tiene ciertas desventajas, como, por ejemplo, que los gráficos con referencias circulares no se pueden serializar. De vez en cuando, es necesario intercambiar a serialización de gráficos de objetos verdadera, incluso aunque no sea interoperable. Esto se puede realizar mediante el uso del DataContractSerializer construido mediante el parámetro preserveObjectReferences establecido en true.

De vez en cuando, los serializadores integrados no son suficientes para su escenario. En la mayoría de los casos, puede seguir utilizando la abstracción XmlObjectSerializer desde la que se derivan DataContractSerializer y NetDataContractSerializer.

Los tres casos anteriores (preservación de tipo .NET, preservación de gráfico de objetos y serialización basada en XmlObjectSerializercompletamente personalizada) requieren que se conecte un serializador personalizado. Para ello, realice los siguientes pasos:

  1. Escriba su propio comportamiento que deriva del DataContractSerializerOperationBehavior.
  2. Invalide los dos métodos CreateSerializer para devolver su propio serializador (NetDataContractSerializer, DataContractSerializer con preserveObjectReferences establecido en true o su propio XmlObjectSerializer personalizado).
  3. Antes de abrir el host de servicio o crear un canal de cliente, elimine el comportamiento DataContractSerializerOperationBehavior existente y conecte la clase derivada personalizada que cree en los pasos anteriores.

Para obtener más información acerca de conceptos avanzados sobre serialización, vea Serialización y deserialización.

Consulte también

Tareas

Cómo habilitar la transmisión
Cómo: Crear un contrato de datos básicos para una clase o estructura

Conceptos

Utilización de la clase XmlSerializer