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. Puede recibir mensajes, procesarlos y enviarlos al código de usuario para realizar más acciones, o puede construir mensajes de datos proporcionados por código de usuario y entregarlos a un destino. En este tema, destinado a desarrolladores avanzados, se describe la arquitectura para controlar los mensajes y los datos contenidos. Para obtener una vista más sencilla y orientada a tareas sobre cómo enviar y recibir datos, consulte Especificar transferencia de datos en contratos de servicio.
Nota:
En este tema se describen los detalles de implementación de WCF que no son visibles examinando el modelo de objetos WCF. Hay dos palabras de precaución con respecto a los detalles de implementación documentados. En primer lugar, las descripciones se simplifican; La implementación real puede ser más compleja debido a optimizaciones u otros motivos. En segundo lugar, nunca debe confiar en detalles de implementación específicos, incluso en los documentados, ya que estos pueden cambiar sin previo aviso de la versión a la versión o incluso en una versión de mantenimiento.
Arquitectura básica
En el núcleo de las funcionalidades de control de mensajes de WCF se encuentra la Message clase , que se describe detalladamente en Uso de la clase message. Los componentes en tiempo de ejecución de WCF se pueden dividir en dos partes principales: la pila de canales y el marco de servicio, con la Message clase siendo el punto de conexión.
La pila de canales es responsable de la conversión entre una instancia válida Message y alguna acción que corresponde al envío o recepción de datos de mensajes. En el lado de envío, la pila del canal toma una instancia válida Message y, después de algún procesamiento, realiza alguna acción que corresponde lógicamente al envío del mensaje. La acción puede enviar paquetes TCP o HTTP, poner en cola el mensaje en Message Queuing, escribir el mensaje en una base de datos, guardarlo en un recurso compartido de archivos o cualquier otra acción, en función de la implementación. La acción más común es enviar el mensaje a través de un protocolo de red. En el lado de recepción, ocurre lo contrario: se detecta una acción (que puede ser paquetes TCP o HTTP que llegan o cualquier otra acción) y, después del procesamiento, la pila del canal convierte esta acción en una instancia válida Message .
Puede utilizar WCF directamente con la pila de canales y la clase Message. Sin embargo, hacerlo es difícil y lento. Además, el Message objeto no proporciona compatibilidad con metadatos, por lo que no puede generar clientes WCF fuertemente tipados si usa WCF de esta manera.
Por lo tanto, WCF incluye un marco de servicio que proporciona un modelo de programación fácil de usar que puede usar para construir y recibir Message
objetos. El marco de servicio asigna servicios a tipos de .NET Framework a través de la noción de contratos de servicio y envía mensajes a operaciones de usuario que son simplemente métodos de .NET Framework marcados con el OperationContractAttribute atributo (para obtener más detalles, vea Diseño de contratos de servicio). Estos métodos pueden tener parámetros y valores devueltos. En el lado del servicio, el marco de servicio convierte las instancias entrantes Message en parámetros y convierte los valores devueltos en instancias salientes Message . En el lado de cliente, hace lo contrario. Por ejemplo, considere la FindAirfare
siguiente operación.
[ServiceContract]
public interface IAirfareFinderService
{
[OperationContract]
int FindAirfare(string FromCity, string ToCity, out bool IsDirectFlight);
}
<ServiceContract()> _
Public Interface IAirfareFinderService
<OperationContract()> _
Function FindAirfare(ByVal FromCity As String, _
ByVal ToCity As String, ByRef IsDirectFlight As Boolean) As Integer
End Interface
Suponga que FindAirfare
se llama en el cliente. El marco de servicio en el cliente convierte los parámetros FromCity
y ToCity
en una instancia saliente Message y lo pasa a la pila de canales para ser enviado.
En el lado del servicio, cuando una instancia Message llega desde el stack de canales, el marco del servicio extrae los datos pertinentes del mensaje para rellenar los parámetros FromCity
y ToCity
, y, a continuación, llama al método FindAirfare
del servicio. Cuando el método devuelve, el marco de servicio toma el valor entero devuelto y el IsDirectFlight
parámetro de salida y crea una Message instancia de objeto que contiene esta información. Luego, se pasa la instancia Message
a la pila de canales para enviarla de regreso al cliente.
En el lado de cliente, una instancia de Message que contiene el mensaje de respuesta emerge de la pila de canales. El marco de trabajo de servicio extrae el valor devuelto y el valor de IsDirectFlight
y los devuelve al llamador del cliente.
Message (clase)
La Message clase está pensada para ser una representación abstracta de un mensaje, pero su diseño está fuertemente vinculado al mensaje SOAP. Contiene Message tres partes principales de información: un cuerpo del mensaje, encabezados de mensaje y propiedades de mensaje.
Cuerpo del mensaje
El cuerpo del mensaje está diseñado para representar la carga de datos real del mensaje. El cuerpo del mensaje siempre se representa como un conjunto de información XML. Esto no significa que todos los mensajes creados o recibidos en WCF deben estar en formato XML. Depende de la pila de canales decidir cómo interpretar el cuerpo del mensaje. Puede emitirlo como XML, convertirlo a otro formato o incluso omitirlo por completo. Por supuesto, con la mayoría de los enlaces que proporciona WCF, el cuerpo del mensaje se representa como contenido XML en la sección body de un sobre SOAP.
Es importante tener en cuenta que la Message
clase no contiene necesariamente un búfer con datos XML que representan el cuerpo. Lógicamente, Message
contiene un conjunto de información XML, pero este conjunto de información se puede construir dinámicamente y nunca existir físicamente en la memoria.
Colocar datos en el cuerpo del mensaje
No hay ningún mecanismo uniforme para colocar datos en un cuerpo del mensaje. La Message clase tiene un método abstracto, OnWriteBodyContents(XmlDictionaryWriter), que toma un XmlDictionaryWriter. Cada subclase de la Message clase es responsable de invalidar este método y escribir su propio contenido. El cuerpo del mensaje contiene lógicamente el conjunto de información XML que OnWriteBodyContent
genera. Por ejemplo, considere la siguiente Message
subclase.
public class AirfareRequestMessage : Message
{
public string fromCity = "Tokyo";
public string toCity = "London";
//code omitted…
protected override void OnWriteBodyContents(XmlDictionaryWriter w)
{
w.WriteStartElement("airfareRequest");
w.WriteElementString("from", fromCity);
w.WriteElementString("to", toCity);
w.WriteEndElement();
}
public override MessageVersion Version
{
get { throw new NotImplementedException("The method is not implemented.") ; }
}
public override MessageProperties Properties
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override MessageHeaders Headers
{
get { throw new Exception("The method or operation is not implemented."); }
}
public override bool IsEmpty
{
get
{
return base.IsEmpty;
}
}
public override bool IsFault
{
get
{
return base.IsFault;
}
}
}
Public Class AirfareRequestMessage
Inherits Message
Public fromCity As String = "Tokyo"
Public toCity As String = "London"
' Code omitted…
Protected Overrides Sub OnWriteBodyContents(ByVal w As XmlDictionaryWriter)
w.WriteStartElement("airfareRequest")
w.WriteElementString("from", fromCity)
w.WriteElementString("to", toCity)
w.WriteEndElement()
End Sub
Public Overrides ReadOnly Property Version() As MessageVersion
Get
Throw New NotImplementedException("The method is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Properties() As MessageProperties
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property Headers() As MessageHeaders
Get
Throw New Exception("The method or operation is not implemented.")
End Get
End Property
Public Overrides ReadOnly Property IsEmpty() As Boolean
Get
Return MyBase.IsEmpty
End Get
End Property
Public Overrides ReadOnly Property IsFault() As Boolean
Get
Return MyBase.IsFault
End Get
End Property
End Class
Físicamente, una AirfareRequestMessage
instancia solo contiene dos cadenas ("fromCity" y "toCity"). Sin embargo, lógicamente el mensaje contiene el siguiente conjunto de información XML:
<airfareRequest>
<from>Tokyo</from>
<to>London</to>
</airfareRequest>
Por supuesto, normalmente no crearía mensajes de esta manera, ya que puede usar el marco de servicio para crear un mensaje como el anterior de los parámetros del contrato de operación. Además, la Message clase tiene métodos estáticos CreateMessage
que se pueden usar para crear mensajes con tipos comunes de contenido: un mensaje vacío, un mensaje que contiene un objeto serializado en XML con DataContractSerializer, un mensaje que contiene un error SOAP, un mensaje que contiene XML representado por un XmlReader, etc.
Obtención de datos del cuerpo del mensaje
Puede extraer los datos almacenados en un cuerpo del mensaje de dos maneras principales:
Puede obtener una vez el cuerpo del mensaje al completo llamando al método WriteBodyContents(XmlDictionaryWriter) y pasando un sistema de escritura de XML. El cuerpo del mensaje al completo se escribe en este sistema de escritura. La obtención de todo el cuerpo del mensaje a la vez también se denomina escritura de un mensaje. La escritura se realiza principalmente mediante la pila de canales al enviar mensajes; parte de la pila de canales normalmente obtendrá acceso a todo el cuerpo del mensaje, lo codificará y lo enviará.
Otra manera de obtener información del cuerpo del mensaje es llamar a GetReaderAtBodyContents() y obtener un lector XML. A continuación, se puede acceder al cuerpo del mensaje de forma secuencial según sea necesario llamando a métodos en el lector. Obtener el cuerpo del mensaje parte por parte también se llama leer un mensaje. La lectura del mensaje se usa principalmente en el marco de servicio al recibir mensajes. Por ejemplo, cuando DataContractSerializer está en uso, el marco de servicio obtendrá un lector XML sobre el cuerpo y lo pasará al motor de deserialización, que luego comenzará a leer el mensaje elemento por elemento y a construir la estructura de objetos correspondiente.
Solo se puede recuperar un cuerpo de mensaje una vez. Esto permite trabajar con secuencias de solo avance. Por ejemplo, puede escribir una invalidación OnWriteBodyContents(XmlDictionaryWriter) que lee de FileStream y devuelve los resultados como un conjunto de información XML. Nunca necesitará "retroceder" al inicio del archivo.
Los métodos WriteBodyContents
y GetReaderAtBodyContents
simplemente comprueban que el cuerpo del mensaje nunca se ha recuperado antes, y luego llaman a OnWriteBodyContents
o OnGetReaderAtBodyContents
, respectivamente.
Uso de mensajes en WCF
La mayoría de los mensajes se pueden clasificar como salientes (aquellos creados por el marco de servicio que va a enviar la pila de canales ) o entrantes (aquellos que llegan desde la pila de canales y que el marco de servicio interpreta). Aún más, la pila de canales puede funcionar en modo almacenado en búfer o modo de transmisión por secuencias. El marco de trabajo de servicio también puede exponer un modelo de programación de transmisión por secuencias o sin transmisión por secuencias. Esto conduce a los casos enumerados en la tabla siguiente, junto con detalles simplificados de su implementación.
Tipo de mensaje | Contenido del mensaje | Implementación de escritura (OnWriteBodyContents) | Implementación de lectura (OnGetReaderAtBodyContents) |
---|---|---|---|
Saliente, creado a partir del modelo de programación sin transmisión por secuencias | Los datos necesarios para escribir el mensaje (por ejemplo, un objeto y la DataContractSerializer instancia necesaria para serializarlo)* | Lógica personalizada para escribir el mensaje en función de los datos almacenados (por ejemplo, llame a WriteObject en DataContractSerializer si es el serializador en uso)* |
Llamar OnWriteBodyContents , almacenar en búfer los resultados, devolver un lector XML sobre el búfer |
Saliente, creado a partir del modelo de programación de transmisión por secuencias | La Stream con los datos que se han de escribir* |
Escriba los datos de la secuencia almacenada utilizando el mecanismo IStreamProvider * | Llamar OnWriteBodyContents , almacenar en búfer los resultados, devolver un lector XML sobre el búfer |
Entrante a partir de la pila de canales de transmisión por secuencias | Un objeto Stream que representa los datos que entran vía la red con un XmlReader sobre él. |
Escriba fuera el contenido del XmlReader almacenado utilizando WriteNode |
Devuelve el XmlReader almacenado |
Entrante a partir de la pila de canales sin transmisión por secuencias | Un búfer que contiene datos del cuerpo con un XmlReader sobre él |
Escribe el contenido del almacenado XmlReader mediante WriteNode |
Devuelve el lenguaje almacenado |
* Estos elementos no se implementan directamente en Message
subclases, sino en subclases de la BodyWriter clase . Para obtener más información sobre BodyWriter, vea Using the Message Class.
Encabezados de mensaje
Un mensaje puede contener encabezados. Un encabezado consta lógicamente de un conjunto de información XML asociado a un nombre, un espacio de nombres y otras propiedades. Se accede a los encabezados de mensaje mediante la Headers
propiedad en Message. Cada encabezado se representa mediante una MessageHeader clase . Normalmente, los encabezados de mensaje se asignan a los encabezados de mensaje SOAP cuando se usa una pila de canales configurada para trabajar con mensajes SOAP.
Colocar información en un encabezado de mensaje y extraer información de él es similar al uso del cuerpo del mensaje. El proceso se simplifica un poco porque no se admite el streaming. Es posible acceder a los contenidos del mismo encabezado más de una vez, y los encabezados pueden ser accedidos en un orden arbitrario, lo que obliga a mantener los encabezados siempre almacenados en búfer. No hay ningún mecanismo de uso general disponible para obtener un lector XML sobre un encabezado, pero hay una MessageHeader
subclase interna para WCF que representa un encabezado legible con esta funcionalidad. La pila del canal crea este tipo de MessageHeader
cuando llega un mensaje con encabezados personalizados de la aplicación. Esto permite que el marco de servicio use un motor de deserialización, como , DataContractSerializerpara interpretar estos encabezados.
Para obtener más información, vea Usar la clase message.
Propiedades del mensaje
Un mensaje puede contener propiedades. Una propiedad es cualquier objeto de .NET Framework asociado a un nombre de cadena. Se tiene acceso a las propiedades a través de la propiedad Properties
en Message
.
A diferencia del cuerpo del mensaje y los encabezados de mensaje (que normalmente se asignan al cuerpo SOAP y a los encabezados SOAP, respectivamente), las propiedades del mensaje normalmente no se envían ni reciben junto con los mensajes. Las propiedades del mensaje existen principalmente como un mecanismo de comunicación para pasar datos sobre el mensaje entre los distintos canales de la pila de canales y entre la pila de canales y el modelo de servicio.
Por ejemplo, el canal de transporte HTTP incluido como parte de WCF es capaz de generar varios códigos de estado HTTP, como "404 (no encontrado)" y "500 (error interno del servidor)," cuando envía respuestas a los clientes. Antes de enviar un mensaje de respuesta, comprueba si el Properties
de Message
contiene una propiedad denominada "httpResponse" que contiene un objeto de tipo HttpResponseMessageProperty. Si se encuentra dicha propiedad, examinará la StatusCode propiedad y usará ese código de estado. Si no se encuentra, se usa el código predeterminado "200 (OK)".
Para obtener más información, vea Usar la clase message.
El mensaje como un todo
Hasta ahora, hemos analizado métodos para acceder a las distintas partes del mensaje de forma aislada. Sin embargo, la Message clase también proporciona métodos para trabajar con todo el mensaje como un todo. Por ejemplo, el método WriteMessage
escribe todo el mensaje en un escritor de XML.
Para que esto sea posible, se debe definir una asignación entre la instancia completa de Message
y un XML Infoset. De hecho, existe esta asignación: WCF usa el estándar SOAP para definir esta asignación. Cuando una Message
instancia se escribe como un conjunto de información XML, el conjunto de información resultante es el sobre SOAP válido que contiene el mensaje. Por lo tanto, WriteMessage
normalmente realizaría los pasos siguientes:
Escribir la etiqueta de apertura del elemento de envoltura SOAP.
Escriba la etiqueta de apertura del elemento de encabezado SOAP, escriba todos los encabezados y cierre el elemento de encabezado.
Escribir la etiqueta de apertura del elemento de cuerpo SOAP.
Llamar a
WriteBodyContents
o a un método equivalente para escribir el cuerpo.Cerrar los elementos de envoltura y cuerpo.
Los pasos anteriores están estrechamente vinculados al estándar SOAP. Esto es complicado por el hecho de que existen varias versiones de SOAP, por ejemplo, es imposible escribir correctamente el elemento de sobre SOAP sin conocer la versión SOAP en uso. Asimismo, en algunos casos, puede ser deseable desactivar completamente esta compleja asignación específica del SOAP.
Para estos fines, se proporciona una propiedad Version
en Message
. Puede establecerse como la versión de SOAP que se va a utilizar cuando se escriba el mensaje, o puede establecerse como None
para evitar cualquier asignación específica de SOAP. Si la Version
propiedad se establece en None
, los métodos que funcionan con todo el mensaje actúan como si el mensaje solo constase de su cuerpo, por ejemplo, WriteMessage
simplemente llamaría a WriteBodyContents
en lugar de realizar los varios pasos enumerados anteriormente. Se espera que, en los mensajes entrantes, Version
se detecte automáticamente y se establezca correctamente.
Pila de canales
Canales
Como se indicó antes, la pila de canales es responsable de convertir instancias salientes Message en alguna acción (como enviar paquetes a través de la red) o convertir alguna acción (como la recepción de paquetes de red) en instancias entrantes Message
.
La pila de canales se compone de uno o varios canales ordenados en una secuencia. Una instancia saliente Message
se pasa al primer canal de la pila (también denominado canal superior), que lo pasa al siguiente canal hacia abajo en la pila, etc. El mensaje finaliza en el último canal, que se denomina canal de transporte. Los mensajes entrantes se originan en el canal de transporte y se pasan de canal en canal hacia arriba en la pila. Desde el canal más alto, el mensaje normalmente se pasa al marco de servicio. Aunque este es el patrón habitual para los mensajes de aplicación, algunos canales pueden funcionar de forma ligeramente diferente, por ejemplo, pueden enviar sus propios mensajes de infraestructura sin pasar un mensaje de un canal anterior.
Los canales pueden funcionar en el mensaje de varias maneras a medida que atraviesa la pila. La operación más común es agregar un encabezado a un mensaje saliente y leer encabezados en un mensaje entrante. Por ejemplo, un canal puede calcular la firma digital de un mensaje y agregarla como encabezado. Un canal también puede inspeccionar este encabezado de firma digital en los mensajes entrantes y bloquear los mensajes que no tienen una firma para que no asciendan en la pila de canales. Los canales también suelen establecer o inspeccionar las propiedades del mensaje. Normalmente, el cuerpo del mensaje no se modifica, aunque esto se permite, por ejemplo, el canal de seguridad WCF puede cifrar el cuerpo del mensaje.
Canales de transporte y codificadores de mensajes
El canal más bajo de la pila es el responsable de la transformación real de un Messagesaliente, tal y como lo modificaron otros canales, en alguna acción. En el lado de recepción, este es el canal que convierte alguna acción en un Message
que otros canales procesan.
Como se dijo anteriormente, se pueden variar las acciones: envío o recepción de paquetes de red a través de varios protocolos, lectura o escritura del mensaje en una base de datos o puesta o eliminación de la cola en una cola de Message Queuing, por mencionar unos pocos ejemplos. Todas estas acciones tienen una cosa en común: requieren una transformación entre la instancia de WCFMessage
y un grupo real de bytes que se pueden enviar, recibir, leer, escribir, encolar o desencolar. El proceso de conversión de un Message
objeto en un grupo de bytes se denomina codificación y el proceso inverso de crear a Message
partir de un grupo de bytes se denomina descodificación.
La mayoría de los canales de transporte usan componentes denominados codificadores de mensajes para lograr el trabajo de codificación y descodificación. Un codificador de mensajes es una subclase de la MessageEncoder clase .
MessageEncoder
incluye varias sobrecargas de método ReadMessage
y WriteMessage
para convertir entre Message
y grupos de bytes.
En el lado de envío, un canal de transporte de almacenado en búfer pasa el objeto Message
que recibió de un canal situado sobre él para WriteMessage
. Obtiene una matriz de bytes, que luego usa para realizar su acción (por ejemplo, empaquetar estos bytes como paquetes TCP válidos y enviarlos al destino correcto). Un canal de transporte de transmisión por secuencia crea primero una Stream
(por ejemplo, sobre la conexión TCP de salida) y, a continuación, pasa la Stream
y el Message
que necesita para enviar a la sobrecarga WriteMessage
adecuada, que escribe el mensaje.
En el lado de recepción, un canal de transporte de almacenado en búfer extrae los bytes de entrada (por ejemplo, de los paquetes TCP entrantes) en una matriz y llama ReadMessage
para obtener un objeto Message
que puede pasar hacia arriba en la pila de canales. El canal de transporte de streaming crea un Stream
objeto (por ejemplo, una secuencia de red a través de la conexión TCP entrante) y lo pasa a ReadMessage
para recuperar un Message
objeto.
La separación entre los canales de transporte y el codificador de mensajes no es obligatorio; es posible escribir un canal de transporte que no use un codificador de mensajes. Sin embargo, la ventaja de esta separación es la facilidad de composición. Siempre que un canal de transporte use solo la base MessageEncoder, puede trabajar con cualquier codificador de mensajes de WCF o de terceros. Del mismo modo, el mismo codificador se puede usar normalmente en cualquier canal de transporte.
Operación del codificador de mensajes
Para describir el funcionamiento típico de un codificador, resulta útil tener en cuenta los cuatro casos siguientes.
Operación | Comentario |
---|---|
Codificación, almacenado en búfer | En el modo almacenado en búfer, el codificador normalmente crea un búfer de tamaño variable y, a continuación, crea un escritor XML sobre él. A continuación, llama a WriteMessage(XmlWriter) en el mensaje que se está codificando que escribe los encabezados y, a continuación, el cuerpo mediante WriteBodyContents(XmlDictionaryWriter), como se explicó en la sección anterior sobre Message en este tema. A continuación, se devuelve el contenido del búfer (representado como una matriz de bytes) para que lo use el canal de transporte. |
Codificación, secuenciada | En el modo transmitido, la operación es similar a la anterior, pero más sencilla. No es necesario un búfer. Un sistema de escritura de XML se crea normalmente sobre la secuencia y se llama a WriteMessage(XmlWriter) en el Message para escribirlo en este sistema de escritura. |
Decodificación, almacenado en búfer | Al descodificar en modo almacenado en búfer, normalmente se crea una subclase especial Message que contiene los datos almacenados en búfer. Los encabezados del mensaje se leen y se crea un lector XML situado en el cuerpo del mensaje. Éste es el lector que se devolverá con GetReaderAtBodyContents(). |
Decodificación, secuenciada | Al descodificar en modo transmitido, normalmente se crea una subclase especial de Mensaje. La secuencia se avanza lo suficiente para leer todos los encabezados y colocarlos en el cuerpo del mensaje. A continuación, se crea un lector XML sobre la secuencia. Éste es el lector que se devolverá con GetReaderAtBodyContents(). |
Los codificadores también pueden realizar otras funciones. Por ejemplo, los codificadores pueden agrupar lectores y escritores XML. Es costoso crear un nuevo lector XML o escritor cada vez que se necesita uno. Por lo tanto, los codificadores normalmente mantienen un grupo de lectores y un grupo de escritores de tamaño configurable. En las descripciones de la operación de codificador descrita anteriormente, siempre que se use la frase "crear un lector/escritor XML", normalmente significa "tomar uno del conjunto o crear uno si no está disponible". El codificador (y las subclases que crea durante la descodificación Message
) contienen lógica para devolver lectores y escritores a los conjuntos una vez que ya no sean necesarios (por ejemplo, cuando se cierra Message
).
WCF proporciona tres codificadores de mensajes, aunque es posible crear tipos personalizados adicionales. Los tipos proporcionados son Text, Binary y Message Transmission Optimization Mechanism (MTOM). Estos se describen en detalle en Elegir un codificador de mensajes.
Interfaz IStreamProvider
Al escribir un mensaje saliente que contiene un cuerpo transmitido a un sistema de escritura XML, Message utilizará una secuencia de llamadas similar a la siguiente en su implementación OnWriteBodyContents(XmlDictionaryWriter) :
Escriba cualquier información necesaria anterior a la secuencia (por ejemplo, la etiqueta XML de apertura).
Escriba la secuencia.
Escriba cualquier información que siga al flujo (por ejemplo, la etiqueta de cierre de XML).
Esto funciona bien con codificaciones similares a la codificación XML textual. Sin embargo, algunas codificaciones no colocan información del conjunto de información XML (por ejemplo, etiquetas para elementos XML iniciales y finales) junto con los datos contenidos en los elementos. Por ejemplo, en la codificación MTOM, el mensaje se divide en varias partes. Una parte contiene el conjunto de información XML, que puede contener referencias a otras partes para el contenido real del elemento. Normalmente, el conjunto de información XML es pequeño en comparación con el contenido transmitido, por lo que tiene sentido almacenar en búfer el conjunto de información, escribirlo y, a continuación, escribir el contenido de forma transmitida. Esto significa que cuando se escribe la etiqueta del elemento de cierre, no se debería haber escrito todavía la secuencia.
Para ello, se usa la IStreamProvider interfaz . La interfaz tiene un GetStream() método que devuelve la secuencia que se va a escribir. La manera correcta de escribir un cuerpo de mensaje transmitido en OnWriteBodyContents(XmlDictionaryWriter) es la siguiente:
Escriba cualquier información necesaria anterior a la secuencia (por ejemplo, la etiqueta XML de apertura).
Llame a la sobrecarga
WriteValue
en el XmlDictionaryWriter que toma IStreamProvider, con una implementaciónIStreamProvider
que devuelve la secuencia que se va a escribir.Escriba cualquier información que siga al flujo (por ejemplo, la etiqueta de cierre de XML).
Con este enfoque, el escritor XML tiene la opción de cuándo llamar GetStream() y escribir los datos transmitidos. Por ejemplo, los escritores XML textuales y binarios lo llamarán inmediatamente y escribirán el contenido transmitido entre las etiquetas de inicio y fin. El sistema de escritura de MTOM puede decidir llamar GetStream() posteriormente, cuando esté listo para escribir la parte adecuada del mensaje.
Representación de datos en Service Framework
Como se indica en la sección "Arquitectura básica" de este tema, el marco de servicio es la parte de WCF que, entre otras cosas, es responsable de la conversión entre un modelo de programación fácil de usar para los datos de mensajes y las instancias reales Message
. Normalmente, un intercambio de mensajes se representa en el marco de servicio como un método de .NET Framework marcado con el OperationContractAttribute atributo . El método puede tomar algunos parámetros y puede devolver un valor de retorno o parámetros de salida (o ambos). En el lado del servicio, los parámetros de entrada representan el mensaje entrante y los parámetros de valor devuelto y salida representan el mensaje saliente. En el lado del cliente, lo contrario es cierto. El modelo de programación para describir los mensajes mediante parámetros y el valor de retorno se describe en detalle en Especificación de la transferencia de datos en los contratos de servicio. Sin embargo, esta sección proporcionará una breve introducción.
Modelos de programación
El marco de servicio WCF admite cinco modelos de programación diferentes para describir los mensajes:
1. Mensaje vacío
Este es el caso más sencillo. Para describir un mensaje entrante vacío, no use ningún parámetro de entrada.
[OperationContract]
int GetCurrentTemperature();
<OperationContract()> Function GetCurrentTemperature() As Integer
Para describir un mensaje saliente vacío, use un valor devuelto void y no use ningún parámetro out:
[OperationContract]
void SetDesiredTemperature(int t);
<OperationContract()> Sub SetDesiredTemperature(ByVal t As Integer)
Tenga en cuenta que esto es diferente de un contrato de operación unidireccional:
[OperationContract(IsOneWay = true)]
void SetLightbulb(bool isOn);
<OperationContract(IsOneWay:=True)> Sub SetLightbulb(ByVal isOn As Boolean)
En el SetDesiredTemperature
ejemplo, se describe un patrón de intercambio de mensajes bidireccional. Se devuelve un mensaje de la operación, pero está vacío. Es posible devolver un error de la operación. En el ejemplo "Establecer bombilla", el patrón de intercambio de mensajes es unidireccional, por lo que no hay ningún mensaje saliente que describir. El servicio no puede comunicar ningún estado al cliente en este caso.
2. Usar directamente la clase de mensaje
Es posible usar la Message clase (o una de sus subclases) directamente en un contrato de operación. En este caso, el marco de trabajo de servicio simplemente pasa el Message
de la operación a la pila de canales y viceversa, sin procesamiento adicional.
Hay dos casos de uso principales para usar Message
directamente. Puede usarlo para escenarios avanzados, cuando ninguno de los otros modelos de programación proporciona suficiente flexibilidad para describir el mensaje. Por ejemplo, es posible que quiera usar archivos en el disco para describir un mensaje, con las propiedades del archivo convirtiéndose en encabezados de mensaje y el contenido del archivo se convierte en el cuerpo del mensaje. A continuación, puede crear algo similar al siguiente.
public class FileMessage : Message
// Code not shown.
Public Class FileMessage
Inherits Message
' Code not shown.
El segundo uso común para Message
en un contrato de operación es cuando un servicio no se preocupa por el contenido del mensaje concreto y actúa sobre el mensaje como en un cuadro negro. Por ejemplo, puede tener un servicio que reenvíe los mensajes a varios otros destinatarios. El contrato se puede escribir de la siguiente manera.
[OperationContract]
public FileMessage GetFile()
{
//code omitted…
FileMessage fm = new FileMessage("myFile.xml");
return fm;
}
<OperationContract()> Public Function GetFile() As FileMessage
'code omitted…
Dim fm As New FileMessage("myFile.xml")
Return fm
End Function
La línea Action="*" desactiva eficazmente el envío de mensajes y garantiza que todos los mensajes enviados al IForwardingService
contrato lleguen a la ForwardMessage
operación. (Normalmente, el despachador examinaría el encabezado "Acción" del mensaje para determinar para qué operación está destinado. Acción="*" significa "todos los valores posibles del encabezado de Acción". La combinación de Acción="*" y el uso de Message como parámetro se conoce como "contrato universal" porque puede recibir todos los mensajes posibles.) Para poder enviar todos los mensajes posibles, use Message como valor devuelto y establezca ReplyAction
en "*". Esto impedirá que el marco de servicio agregue su propio encabezado Action, lo que le permitirá controlar este encabezado mediante el Message
objeto que devuelve.
3. Contratos de mensajes
WCF proporciona un modelo de programación declarativo para describir los mensajes, denominados contratos de mensajes. Este modelo se describe en detalle en Uso de contratos de mensajes. Básicamente, todo el mensaje se representa mediante un único tipo de .NET Framework que utiliza atributos como MessageBodyMemberAttribute y MessageHeaderAttribute para describir qué partes de la clase del contrato de mensaje deben asignarse a qué parte del mensaje.
Los contratos de mensajes ofrecen un gran control sobre las instancias resultantes Message
(aunque, obviamente, no tanto como el control al utilizar directamente la clase Message
). Por ejemplo, los cuerpos de mensaje suelen estar compuestos por varios fragmentos de información, cada uno representado por su propio elemento XML. Estos elementos pueden aparecer directamente en el cuerpo (modo desnudo) o pueden envolverse en un elemento XML envolvente. Utilizar el modelo de programación de contrato de mensajes permite decidir entre desnudo y ajustado y controlar el nombre del nombre del contenedor y del espacio de nombres.
En el ejemplo de código siguiente de un contrato de mensaje se muestran estas características.
[MessageContract(IsWrapped = true, WrapperName = "Order")]
public class SubmitOrderMessage
{
[MessageHeader]
public string customerID;
[MessageBodyMember]
public string item;
[MessageBodyMember]
public int quantity;
}
<MessageContract(IsWrapped:=True, WrapperName:="Order")> _
Public Class SubmitOrderMessage
<MessageHeader()> Public customerID As String
<MessageBodyMember()> Public item As String
<MessageBodyMember()> Public quantity As Integer
End Class
Los elementos marcados para serializarse (con MessageBodyMemberAttribute, MessageHeaderAttribute u otros atributos relacionados) deben ser serializables para participar en un contrato de mensaje. Para obtener más información, vea la sección "Serialización" más adelante en este tema.
4. Parámetros
A menudo, un desarrollador que quiere describir una operación que actúa en varios fragmentos de datos no necesita el grado de control que proporcionan los contratos de mensajes. Por ejemplo, al crear los nuevos servicios, uno normalmente no desea tomar la decisión de desnudo frente a ajustado, sino decidirse por el nombre del elemento contenedor. La toma de estas decisiones a menudo requiere un profundo conocimiento de los servicios web y SOAP.
El marco de servicio WCF puede elegir automáticamente la representación SOAP mejor e interoperable para enviar o recibir varios fragmentos de información relacionados, sin forzar estas opciones en el usuario. Esto se logra simplemente mediante la descripción de estos fragmentos de información como parámetros o valores devueltos de un contrato de operación. Por ejemplo, considere el siguiente contrato de operación.
[OperationContract]
void SubmitOrder(string customerID, string item, int quantity);
<OperationContract()> _
Sub SubmitOrder( _
ByVal customerID As String, _
ByVal item As String, _
ByVal quantity As Integer)
El marco de servicio decide colocar automáticamente los tres fragmentos de información (customerID
, item
y quantity
) en el cuerpo del mensaje y encapsularlos en un elemento contenedor denominado SubmitOrderRequest
.
Describir la información que se va a enviar o recibir como una lista sencilla de parámetros de contrato de operación es el enfoque recomendado, a menos que existan razones especiales para pasar al contrato de mensajes más complejo o Message
a los modelos de programación basados en .
5. Secuencia
El uso Stream
de o una de sus subclases en un contrato de operación o como una única parte del cuerpo del mensaje en un contrato de mensaje se puede considerar un modelo de programación independiente de los descritos anteriormente. Utilizar Stream
de esta manera es la única manera de garantizar que su contrato se podrá usar de manera secuenciada, sin tener que escribir su propia subclase Message
compatible con transmisión por secuencias. Para obtener más información, consulte Datos grandes y streaming.
Cuando Stream
o una de sus subclases se usan de esta manera, el serializador no es invocado. En el caso de los mensajes salientes, se crea una subclase de transmisión Message
especial y la transmisión se realiza según lo descrito en la sección sobre la interfaz IStreamProvider. Para los mensajes entrantes, el marco de servicio crea una Stream
subclase sobre el mensaje entrante y la proporciona a la operación.
Restricciones del modelo de programación
Los modelos de programación descritos anteriormente no se pueden combinar arbitrariamente. Por ejemplo, si una operación acepta un tipo de contrato de mensaje, el contrato de mensaje debe ser su único parámetro de entrada. Lo que es más, la operación debe devolver a continuación un mensaje vacío (tipo de valor devuelto de vacío) u otro contrato de mensajes. Estas restricciones del modelo de programación se describen en los temas de cada modelo de programación específico: Uso de contratos de mensajes, Uso de la clase de mensaje y datos de gran tamaño y transmisión.
Formateadores de mensajes
Los modelos de programación descritos anteriormente se admiten conectando componentes denominados formateadores de mensajes en el marco de servicio. Los formateadores de mensajes son tipos que implementan la interfaz IClientMessageFormatter o IDispatchMessageFormatter o ambas, para el uso en clientes y clientes WCF de servicio, respectivamente.
A los formateadores de mensajes se le agregan normalmente comportamientos. Por ejemplo, DataContractSerializerOperationBehavior agrega el formateador de mensajes de contratos de datos. Esto se hace en el lado del servicio estableciendo Formatter en el formateador correcto en el ApplyDispatchBehavior(OperationDescription, DispatchOperation) método o en el lado cliente estableciendo Formatter en el formateador correcto en el ApplyClientBehavior(OperationDescription, ClientOperation) método .
En las tablas siguientes se enumeran los métodos que un formateador de mensajes puede implementar.
Interfaz | Método | Acción |
---|---|---|
IDispatchMessageFormatter | DeserializeRequest(Message, Object[]) | Convierte un Message entrante en parámetros de operación. |
IDispatchMessageFormatter | SerializeReply(MessageVersion, Object[], Object) | Crea un Message saliente a partir del valor devuelto/parámetros out de la operación |
IClientMessageFormatter | SerializeRequest(MessageVersion, Object[]) | Crea un Message saliente a partir de los parámetros de operación |
IClientMessageFormatter | DeserializeReply(Message, Object[]) | Convierte una entrada Message a un valor devuelto o parámetros de salida |
Serialización
Siempre que use contratos de mensajes o parámetros para describir el contenido del mensaje, debe usar la serialización para convertir entre tipos de .NET Framework y la representación del conjunto de información XML. La serialización se usa en otros lugares de WCF, por ejemplo, Message tiene un método Generic GetBody que se puede usar para leer todo el cuerpo del mensaje deserializado en un objeto .
WCF admite dos tecnologías de serialización de fábrica para serializar y deserializar parámetros y partes de mensajes: DataContractSerializer y XmlSerializer
. Además, puede escribir serializadores personalizados. Sin embargo, otras partes de WCF (como el método genérico GetBody
o la serialización de errores SOAP) pueden estar limitadas para utilizar solamente las subclases XmlObjectSerializer (DataContractSerializer y NetDataContractSerializer, pero no la XmlSerializer), o incluso pueden estar codificadas de manera fija para utilizar únicamente la DataContractSerializer.
XmlSerializer
es el motor de serialización que se usa en ASP.NET servicios web.
DataContractSerializer
es el nuevo motor de serialización que comprende el nuevo modelo de programación de contratos de datos.
DataContractSerializer
es la opción predeterminada y la opción de usar XmlSerializer
se puede realizar por cada operación mediante el DataContractFormatAttribute atributo .
DataContractSerializerOperationBehavior y XmlSerializerOperationBehavior son los comportamientos de operación responsables de conectar los formateadores de mensaje para DataContractSerializer
y XmlSerializer
, respectivamente. El comportamiento DataContractSerializerOperationBehavior puede realmente funcionar con cualquier serializador que derive de XmlObjectSerializer, incluso NetDataContractSerializer (descrito en detalle en Uso de serialización independiente). El comportamiento llama a una de las sobrecargas de método virtual CreateSerializer
para obtener el serializador. Para conectar un serializador diferente, cree una nueva DataContractSerializerOperationBehavior subclase e invalide ambas CreateSerializer
sobrecargas.