Utilización de la clase de mensajes
La clase Message es fundamental para Windows Communication Foundation (WCF). Toda la comunicación entre clientes y servicios, en última instancia, produce como resultado instancias Message que se envían y reciben.
Normalmente no interactuaría directamente con la clase Message. En su lugar, construcciones de modelo de servicio WCF, como contratos de datos, contratos del mensaje y contratos de operación, se utilizan para describir los mensajes de entrada y salida. Sin embargo, en algunos escenarios avanzados puede programar utilizando directamente la clase Message. Por ejemplo, es posible que desee usar la clase Message:
- Cuando necesite una manera alternativa de crear el contenido del mensaje saliente (por ejemplo, crear un mensaje directamente a partir de un archivo en disco) en lugar de serializar los objetos .NET Framework.
- Cuando necesite una manera alternativa de utilizar el contenido del mensaje de entrada (por ejemplo, al desear aplicar una transformación XSLT al contenido de XML sin formato) en lugar de deserializar en los objetos .NET Framework.
- Cuando necesite tratar con mensajes de una manera general sin tener en cuenta el contenido del mensaje (por ejemplo, cuando enrute o reenvíe mensajes al generar un enrutador, equilibrador de carga o un sistema publicación-suscripción
Antes de utilizar la clase Message, familiarícese con la arquitectura de transferencia de datos WCF en Información general sobre la arquitectura de transferencia de datos.
Message es un contenedor de uso general para los datos, pero su diseño sigue estrechamente el diseño de un mensaje en el protocolo SOAP. Simplemente como en SOAP, un mensaje tiene un cuerpo de mensaje y encabezados. El cuerpo del mensaje contiene los datos de carga reales, mientras que los encabezados contienen los contenedores de datos con nombre adicionales. Las reglas para leer y escribir el cuerpo y los encabezados son diferentes; Por ejemplo, los encabezados están almacenados siempre en memoria búfer y se puede tener acceso a ellos todas las veces que se quiera, mientras que el cuerpo se puede leer sólo una vez y puede ser transmitido. Normalmente, cuando se utiliza SOAP, el cuerpo del mensaje está asignado al cuerpo SOAP y los encabezados del mensaje están asignados a los encabezados SOAP.
Utilizar la clase de mensaje en Operaciones
Puede utilizar la clase Message como un parámetro de entrada de una operación, el valor devuelto de una operación, o ambos. Si Message se utiliza en cualquier parte en una operación, se aplican las restricciones siguientes:
- La operación no puede tener ningún out o parámetro ref.
- No puede haber más de un parámetro input. Si el parámetro está presente, debe ser Mensaje o un tipo de contrato de mensaje.
- El tipo de valor devuelto debe ser void, Messageo un tipo de contrato de mensaje.
El ejemplo de código siguiente contiene un contrato de operación válido.
Creación de mensajes básicos
La clase Message proporciona métodos de generación estáticos CreateMessage que se pueden usar para crear mensajes básicos.
Todas las sobrecargas CreateMessage toman un parámetro de versión de tipo MessageVersion que indica el SOAP y versiones de WS-Addressing para utilizarlas para el mensaje. Si desea utilizar las mismas versiones de protocolo que el mensaje de entrada, puede utilizar la propiedad IncomingMessageVersion en la instancia OperationContext obtenida de la propiedad Current. La mayoría de las sobrecargas CreateMessage también tienen un parámetro de cadena que indica la acción SOAP que se utilizará en el mensaje. La versión se puede establecer en None para deshabilitar la generación de la envoltura SOAP; el mensaje solamente se compone del cuerpo.
Crear mensajes a partir de objetos
La sobrecarga CreateMessage más básica que toma sólo una versión y una acción crea un mensaje con un cuerpo vacío. Otra sobrecarga toma un parámetro Object adicional; esto crea un mensaje cuyo cuerpo es la representación serializada del objeto determinado. Utilice DataContractSerializer con configuración predeterminada para la serialización. Si desea utilizar un serializador diferente, o quiere que DataContractSerializer se configure de manera diferente, utilice la sobrecarga CreateMessage que también toma un parámetro XmlObjectSerializer.
Por ejemplo, para devolver un objeto en un mensaje, puede utilizar el siguiente código.
Crear los mensajes a partir de los lectores XML
Hay sobrecargas CreateMessage que toman XmlReader o XmlDictionaryReader para el cuerpo en lugar de un objeto. En este caso, el cuerpo del mensaje contiene el XML que se obtiene al leer con el lector XML pasado. Por ejemplo, el código siguiente devuelve un mensaje con el contenido del cuerpo leído desde un archivo XML.
Hay además, sobrecargas CreateMessage que toman XmlReader o XmlDictionaryReader que representa el mensaje completo y no simplemente el cuerpo. Estas sobrecargas también toman un parámetro maxSizeOfHeaders entero. Los encabezados siempre se almacenan en búfer en la memoria en cuanto se crea el mensaje, y este parámetro limita la cantidad de almacenamiento en búfer que tiene lugar. Es importante establecer este parámetro en un valor seguro si el XML procede de un origen que no es de confianza para mitigar la posibilidad de un ataque por denegación de servicio. El SOAP y versiones de WS-Addressing del mensaje que el lector XML representa deben coincidir con las versiones indicadas utilizando el parámetro de versión.
Crear los mensajes con BodyWriter
Una sobrecarga CreateMessage toma una instancia BodyWriter para describir el cuerpo del mensaje. BodyWriter es una clase abstracta que se puede derivar para personalizar cómo se crean los cuerpos del mensaje. Puede crear su propia clase BodyWriter derivada para describir los cuerpos del mensaje de una manera personalizada. Debe invalidar el método BodyWriter.OnWriteBodyContents que toma XmlDictionaryWriter; este método es el responsable de la escritura del cuerpo.
Los sistemas de escritura del cuerpo se pueden almacenar en búfer o no (se pueden transmitir por secuencias). Los sistemas de escritura del cuerpo almacenados en búfer pueden escribir su contenido todas las veces que se quiera, mientras que los transmitidos sólo pueden escribir sus contenidos una vez. La propiedad IsBuffered indica si un sistema de escritura del cuerpo está almacenado en búfer o no. Puede establecerlo para su sistema de escritura de cuerpo mediante una llamada al constructor BodyWriter protegido que toma un parámetro booleano isBuffered. Los sistemas de escritura de cuerpo permiten crear un sistema de escritura de cuerpo almacenado en búfer a partir de un sistema de escritura de cuerpo no almacenado en búfer. Puede invalidar el método OnCreateBufferedCopy para personalizar este proceso. De forma predeterminada, se usa un búfer en memoria que contiene el XML devuelto por OnWriteBodyContents. OnCreateBufferedCopy toma un parámetro maxBufferSize entero; si invalida este método, no debe crear búferes que superen este tamaño máximo.
La clase BodyWriter proporciona respectivamente WriteBodyContents y los métodos CreateBufferedCopy, que son contenedores esencialmente delgados alrededor de OnWriteBodyContents y los métodos OnCreateBufferedCopy. Estos métodos realizan la comprobación de estado para asegurarse de que no se tiene acceso a un sistema de escritura de cuerpo no almacenado en búfer más de una vez. Sólo se llama a estos métodos directamente al crear clases Message derivadas personalizadas basadas en BodyWriters.
Crear los mensajes de error
Puede utilizar ciertas sobrecargas CreateMessage para crear los mensajes del error de SOAP. La más básica toma un objeto MessageFault que describe el error. Otras sobrecargas se proporcionan para comodidad. La primera sobrecarga toma FaultCode y una cadena de la razón y crea MessageFault mediante MessageFault.CreateFault mediante esta información. La otra sobrecarga toma un objeto de datos y también lo pasa a CreateFault junto con el código de error y la razón. Por ejemplo, la operación siguiente devuelve un error.
Extraer los datos del cuerpo del mensaje
La clase Message admite varias maneras de extraer información de su cuerpo. Éstos pueden estar clasificados en las categorías siguientes:
- Obtener el cuerpo del mensaje completo escrito de una vez en un sistema de escritura de XML. Esto se conoce como escribir un mensaje.
- Obtener un lector XML sobre el cuerpo del mensaje. Esto le permite al acceso posterior el cuerpo del mensaje parte por parte, según se requiera. Esto se conoce como leer un mensaje.
- El mensaje completo, incluido su cuerpo, se puede copiar en un búfer en memoria del tipo MessageBuffer. Esto se conoce como copiar un mensaje.
Sólo puede tener acceso una vez al cuerpo de Message, independientemente de cómo se tiene acceso. Un objeto de mensaje tiene una propiedad State, la cual se establece inicialmente en Creada. Los tres métodos de acceso descritos en la lista anterior establecen el estado en Escrito, Leído, y Copiado, respectivamente. Además, un método Close puede establecer el estado en Cerrado cuando ya no se necesita el contenido del cuerpo del mensaje. Se puede tener acceso al cuerpo del mensaje sólo en el estado Creado y no hay ninguna manera de regresar al estado Creado después de que el estado haya cambiado.
Escribir los mensajes
El método WriteBodyContents escribe el contenido del cuerpo de un Message determinado cree instancias a un sistema de escritura XML determinado. El método WriteBody hace lo mismo, a excepción de que agrega el contenido del cuerpo en el elemento contenedor adecuado (por ejemplo, <soap:body>). Finalmente, WriteMessage escribe el mensaje completo, incluso la envoltura SOAP de ajuste y los encabezados. Si SOAP está desactivado (Version es MessageVersion.None), los tres métodos hacen lo mismo: escriben el contenido del cuerpo del mensaje.
Por ejemplo, el código siguiente escribe el cuerpo de un mensaje de entrada en un archivo.
Dos métodos auxiliares adicionales escriben ciertas etiquetas de elemento de inicio de SOAP. Estos métodos no tienen acceso al cuerpo del mensaje, de modo que no cambian el estado del mensaje. Éstos incluyen:
- WriteStartBody escribe el elemento de cuerpo de inicio, por ejemplo, <soap:Body>.
- WriteStartEnvelope escribe el elemento de envoltura de inicio, por ejemplo, <soap:Envelope>.
Para escribir las etiquetas de elementos finales correspondientes, llame WriteEndElement en el sistema de escritura XML correspondiente. Rara vez se llama a estos métodos directamente.
Leer mensajes
La manera principal para leer un cuerpo del mensaje es llamar GetReaderAtBodyContents. Se recibe XmlDictionaryReader, que se puede usar para leer el cuerpo del mensaje. Tenga en cuenta que Message pasa al estado Leído en cuanto se llama a GetReaderAtBodyContents y no cuando se usa el lector XML devuelto.
El método GetBody también le permite tener acceso al cuerpo del mensaje como un objeto con tipo. Internamente, este método utiliza GetReaderAtBodyContentsy así también pasa el estado del mensaje al estado Read (vea la propiedad State ).
Es recomendable comprobar la propiedad IsEmpty, en cuyo caso el cuerpo del mensaje está vacío y GetReaderAtBodyContents inicia InvalidOperationException. Además, si es un mensaje recibido (por ejemplo, la respuesta), es posible que también desee comprobar IsFault, que indica si el mensaje contiene un error.
La sobrecarga más básica de GetBody deserializa el cuerpo del mensaje en una instancia de un tipo (indicado por el parámetro genérico) mediante el uso de un DataContractSerializer establecido con la configuración predeterminada y con la cuota MaxItemsInObjectGraph deshabilitada. Si desea usar un motor de serialización diferente o configurar DataContractSerializer de una manera distinta a la predeterminada, use la sobrecarga GetBody que toma XmlObjectSerializer.
Por ejemplo, el código siguiente extrae los datos de un cuerpo de mensaje que contiene un objeto Person
serializado e imprime el nombre de la persona.
Copiar un mensaje en un búfer
A veces es necesario tener acceso más de una vez al cuerpo del mensaje, por ejemplo, para reenviar el mismo mensaje a varios destinos como parte de un sistema de publicación-suscripción. En este caso, es necesario almacenar en búfer el mensaje completo (incluido el cuerpo) en memoria. Puede hacerlo llamando CreateBufferedCopy. Este método toma un parámetro entero que representa el tamaño máximo de búfer y crea un búfer inferior a este tamaño. Es importante establecer esto en un valor seguro si el mensaje procede de un origen que no es de confianza.
Se devuelve el búfer como una instancia MessageBuffer. Puede tener acceso a los datos del búfer de varias maneras. El modo principal es llamar CreateMessage para crear las instancias Message a partir del búfer.
Otra manera de tener acceso a los datos del búfer es implementar la interfaz IXPathNavigable que la clase MessageBuffer implementa para tener acceso directamente al XML subyacente. Algunas sobrecargas CreateNavigator permiten crear navegadores System.Xml.XPath protegidos por una cuota de nodo que limita el número de nodos XML que se pueden visitar. Esto ayuda a evitar ataques por denegación de servicio en función del tiempo de procesamiento largo. Esta cuota está deshabilitada de forma predeterminada. Algunas sobrecargas CreateNavigator le permiten especificar cómo se debería administrar el espacio en blanco en el XML utilizando la enumeración XmlSpace, con los valores predeterminados XmlSpace.None.
Una última manera de tener acceso al contenido de un búfer del mensaje es escribir su contenido en una secuencia utilizando WriteMessage.
El ejemplo siguiente muestra el proceso de trabajar con MessageBuffer: un mensaje de entrada se reenvía a varios destinatarios y, a continuación, se registra en un archivo. Sin el almacenamiento en búfer, esto no es posible, porque se puede tener acceso al cuerpo del mensaje una sola vez.
La clase MessageBuffer tiene otro miembros que hay que tener en cuenta. Se puede llamar al método Close para liberar recursos cuando ya no se requiera el contenido del búfer. La propiedad BufferSize devuelve el tamaño del búfer asignado. La propiedad MessageContentType devuelve el tipo de contenido de MIME del mensaje.
Tener acceso al cuerpo del mensaje para depuración
Para la depuración, puede llamar al método ToString con el fin de obtener una representación del mensaje como una cadena. Esta representación por lo general coincide con la manera en que se mostraría un mensaje en la conexión si estuviera codificado con el codificador de texto, con la excepción de que se le aplicaría un formato más legible que XML. La excepción a esto es el cuerpo del mensaje. El cuerpo se puede leer una sola vez y ToString no cambia el estado del mensaje. Por consiguiente, el método ToString no podría tener acceso al cuerpo y podría sustituir un marcador de posición (por ejemplo, “…” o tres puntos) en lugar del cuerpo del mensaje. Por consiguiente, no use ToString para registrar los mensajes si el contenido del cuerpo de los mensajes es importante.
Tener acceso a otras partes del mensaje
Se proporcionan varias propiedades para tener acceso a la información del mensaje que no sea el contenido del cuerpo. Sin embargo, no se puede llamar a éstos una vez se ha cerrado el mensaje:
- La propiedad Headers representa los encabezados del mensaje. Consulte la sección en "Trabajar con encabezados" más adelante en este tema.
- La propiedad Properties representa las propiedades de mensaje, que son partes de datos con nombre asociadas al mensaje que generalmente no se emiten cuando se envía el mensaje. Consulte la sección en "Trabajar con propiedades" más adelante en este tema.
- La propiedad Version indica el SOAP y versión WS-Addressing asociada al mensaje o None si SOAP está deshabilitado.
- La propiedad IsFault devuelve true si el mensaje es un mensaje de error de SOAP.
- La propiedad IsEmpty devuelve true si el mensaje está vacío.
Puede utilizar el método GetBodyAttribute para tener acceso a un atributo determinado en el elemento contenedor del cuerpo (por ejemplo, <soap:Body>) identificado por un nombre y espacio de nombres determinados. Si no se encuentra dicho atributo, se devuelve null. Se puede llamar a este método sólo cuando Message está en el estado Creado (cuando todavía no se ha tenido acceso al cuerpo del mensaje).
Trabajar con encabezados
Un Message puede contener cualquier número de fragmentos XML con nombre, llamados encabezados. Cada fragmento asigna normalmente a un encabezado SOAP. Se accede a los encabezados a través de la propiedad Headers de tipo MessageHeaders. MessageHeaders es una colección de MessageHeaderInfo y se puede tener acceso a los encabezados individuales a través de su interfaz IEnumerable o a través de su indizador. Por ejemplo, el código siguiente hace una lista de los nombres de todos los encabezados en Message.
Agregar, quitar, buscar encabezados
Puede agregar un nuevo encabezado al final de todos los encabezados existentes utilizando el método Add. Puede utilizar el método Insert para insertar un encabezado en un índice determinado. Los encabezados existentes se desplazan para el elemento insertado. Los encabezados se ordenan según su índice y el primer índice disponible es 0. Puede utilizar las diferentes sobrecargas de método CopyHeadersFrom para agregar los encabezados de un Message diferente o instancia MessageHeaders. Algunas sobrecargas copian un encabezado individual, mientras que otras copian todos ellos. El método Clear quita todos los encabezados. El método RemoveAt quita un encabezado en un índice determinado (desplazando todos los encabezados después de él). El método RemoveAll quita todos los encabezados con un nombre y espacio de nombres determinados.
Recupere un encabezado determinado mediante el método FindHeader. Este método toma el nombre y espacio de nombres del encabezado para buscar y devuelve su índice. Si el encabezado se produce más de una vez, se produce una excepción. Si no se encuentra el encabezado, devuelve -1.
En el modelo del encabezado SOAP, los encabezados pueden tener un valor Actor que especifica el destinatario previsto del encabezado. La sobrecarga FindHeader más básica sólo busca en los encabezados que están pensados para el receptor definitivo del mensaje. Sin embargo, otra sobrecarga le permite especificar qué valores Actor están incluidos en la búsqueda. Para obtener más información, consulte la especificación SOAP.
Se proporciona un método CopyTo para copiar los encabezados de una colección MessageHeaders en una matriz de los objetos MessageHeaderInfo.
Para tener acceso a los datos XML en un encabezado, puede llamar GetReaderAtHeader y devolver un lector XML para el índice del encabezado concreto. Si quiere deserializar el contenido del encabezado en un objeto, utilice GetHeader o una de las otras sobrecargas. Las sobrecargas más básicas deserializan los encabezados mediante el uso del DataContractSerializer configurado de la manera predeterminada. Si desea utilizar un serializador diferente o una configuración diferente de DataContractSerializer, utilice una de las sobrecargas que toman XmlObjectSerializer. También hay sobrecarga que toma el nombre del encabezado, espacio de nombres y opcionalmente una lista de los valores Actor en lugar de un índice; ésta es una combinación de FindHeader y GetHeader.
Trabajar con propiedades
Una instancia Message puede contener un número arbitrario de objetos con nombre de tipos arbitrarios. Se accede a esta colección a través de la propiedadProperties de tipo MessageProperties. La colección implementa la interfaz IDictionary y actúa como una asignación de String a Object. Normalmente, los valores de la propiedad no se asignan directamente a cualquier parte del mensaje de la conexión, sino que proporcionan varias sugerencias de procesamiento de mensajes a los diferentes canales de la pila del canal WCF o al marco del servicio CopyTo. Vea un ejemplo en Información general sobre la arquitectura de transferencia de datos.
Herencia de la clase de mensaje
Si los tipos de mensaje integrados creados mediante CreateMessage no cumplen sus requisitos, cree una clase que derive de la clase Message.
Definir el contenido del cuerpo del mensaje
Existen tres técnicas primarias para tener acceso a los datos dentro de un cuerpo del mensaje: escribir, leer y copiar en un búfer. En última instancia, como consecuencia de estas operaciones se llama a los métodos OnWriteBodyContents, OnGetReaderAtBodyContents y OnCreateBufferedCopy, respectivamente, en la clase derivada de Message. La clase base Message garantiza que se llamará a uno de estos métodos por cada instancia de Message y que no se llama más de una vez. La clase base también garantiza que no se llama a los métodos en un mensaje cerrado. No hay ninguna necesidad de realizar el seguimiento del estado del mensaje en su implementación.
OnWriteBodyContents es un método abstracto y se implementa. La manera más básica de definir el contenido del cuerpo de un mensaje es escribirlo con este método. Por ejemplo, el mensaje siguiente contiene 100,000 números aleatorios de 1 a 20.
OnGetReaderAtBodyContents y los métodos OnCreateBufferedCopy tienen implementaciones predeterminadas que funcionan en la mayoría de los casos. Las implementaciones predeterminadas llaman a OnWriteBodyContents, almacenan en búfer los resultados y funcionan con el búfer resultante. Sin embargo, en algunos casos esto puede no ser bastante. En el ejemplo anterior, leer el mensaje resulta en 100,000 elementos XML almacenados en búfer, lo cual podría no ser deseable. Es posible que desee invalidar OnGetReaderAtBodyContents para devolver una clase XmlDictionaryReader derivada personalizada que proporcione números aleatorios. Puede invalidar a continuación OnWriteBodyContents para utilizar el lector que devuelve la propiedad OnGetReaderAtBodyContents, tal y como se muestra en el ejemplo siguiente.
De igual forma, podría desear invalidar OnCreateBufferedCopy para devolver su propia clase derivada MessageBuffer.
Además de proporcionar el contenido del cuerpo del mensaje, la clase derivada del mensaje también debe invalidar las propiedades Version, Headers y Properties.
Tenga en cuenta que si crea una copia de un mensaje, la copia utiliza los encabezados del mensaje del original.
Otros miembros que se pueden invalidar
Puede invalidar OnWriteStartEnvelope, OnWriteStartHeadersy los métodos OnWriteStartBody para especificar cómo se escribe la envoltura SOAP, encabezados SOAP y etiquetas iniciales del elemento de cuerpo SOAP. Éstos corresponden normalmente a <soap:Envelope>, **<soap:Header>**y <soap:Body>. Estos métodos no deberían escribir normalmente nada si la propiedad Version devuelve MessageVersion.None.
Nota
La implementación predeterminada de OnGetReaderAtBodyContents llama OnWriteStartEnvelope y OnWriteStartBody antes de llamar OnWriteBodyContents y almacenar en búfer los resultados. Los encabezados no se escriben.
Invalide el método OnWriteMessage para cambiar la manera de construir el mensaje completo a partir de sus diferentes partes. El método OnWriteMessage es llamado por WriteMessage y por la implementación predeterminada OnCreateBufferedCopy. Tenga en cuenta que invalidar WriteMessage no es un procedimiento recomendado. Es mejor invalidar los métodos On
adecuados (por ejemplo, OnWriteStartEnvelope, OnWriteStartHeadersy OnWriteBodyContents.
Invalide OnBodyToString para invalidar cómo el cuerpo del mensaje se representa durante la depuración. El valor predeterminado es representarlo con tres puntos ("..."). Tenga en cuenta que se puede llamar a este método varias veces cuando el estado del mensaje es otro distinto de Cerrado. Una implementación de este método no debería producir nunca una acción que sólo se deba realizar una vez (como leer de una secuencia sólo hacia adelante).
Invalide el método OnGetBodyAttribute para permitir el acceso a los atributos en el elemento de cuerpo SOAP. Este método se puede llamar todas las veces que se desee, pero el tipo base Message garantiza que sólo se llama cuando el mensaje se encuentre en estado Creado. No se requiere para comprobar el estado en una implementación. La implementación predeterminada siempre devuelve null, lo que indica que no hay ningún atributo en el elemento del cuerpo.
Si el objeto Message debe llevar a cabo alguna operación de limpieza especial cuando ya no se necesita el cuerpo del mensaje, puede invalidar OnClose. La implementación predeterminada no hace nada.
IsEmpty y las propiedades IsFault se pueden invalidar. De forma predeterminada, ambos devuelven false.