miércoles, 15 de septiembre de 2010

[WCF] Codificaciones de texto y controlando la serialización mediante eventos

El motor de serialización de WCF está limitado a unos cuantos formatos de codificación y por defecto siempre codifica el tipo string en el formato UTF8. A priori no hay ningún problema, pero te puede ocurrir el siguiente escenario:

Tenemos una serie de entidades serializadas con WCF en las que se almacena texto. Por ejemplo, objetos de negocio como artículos en una tienda en la que algunos de sus campos son la descripción, el nombre del artículo, además de todos aquellos que se crean necesarios. Partamos de la base de que hemos serializado nuestro objeto siguiendo el artículo correspondiente. En un momento determinado, comienzas a escribir la información de los objetos en el XML, de un modo a como sigue:

<ArticleManager xmlns="http://schemas.datacontract.org/2004/07/Logic" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

<Articles>

<Article>

<Name>Halo Reach</Name>

<Description>La apasionante última entrega de la exitosa saga de shooters. </Description>

</Article>

</ArticleManager>

Para una codificacion en UTF-8 no tendríamos ningún problema en deserializar el campo Name, puesto que todos los caracteres son válidos en bytes desde el punto de vista de esa codificación. Sin embargo, no ocurre lo mismo con el campo Description. Este campo incluye caracteres que no son válidos para esa coficación, como por ejemplo los caracteres con tildes (en última) y otros propios del castellano (como el caracter ñ). En el momento en que se deserializa ese dato, provoca una excepción en XmlSerializer por no encontrarse dentro del esquema de UTF8 y similares. ¿Qué podemos hacer? Muchas cosas, como por ejemplo establecer un XmlSerializer propio para nuestras clases de modo que los campos de tipo string se codifiquen y decodifiquen empleando las codificaciones que queramos, crear un decodificador propio exclusivo para manejar texto o bien lo que propongo aquí, que consiste en almacenar la información con bytes de cara al fichero XML pero mostrándola al usuario como string controlando mediante eventos los procesos de serialización y deserialización. Veamos cómo se puede hacer:

Nuestra clase artículo tiene el siguiente juego de atributos:

   1: [DataContract()]
   2: public class Article
   3: {
   4:     [DataMember()]
   5:     public string Name;
   6:     [DataMember()]
   7:     public string Description;
   8: }

Y lo que vamos a hacer es transformar y encapsular los campos miembros serializables dentro de arrays de bytes:

   1: [DataContract()]
   2: public class Article
   3: {
   4:     [DataMember()]
   5:     public byte[] ByteName;
   6:     [DataMember()]
   7:     public byte[] ByteDescription;
   8:  
   9:     public string Name;
  10:     public string Description;
  11:     ...
  12: }

La idea es sencilla y se expuso anteriormente pero la recuerdo. Vamos a guardar los campos en el xml como bytes y al cargar el fichero, automáticamente se transforma en string. Para ello se proporcionan una serie de etiquetas de eventos que se pueden asociar a funciones de la clase que sufre el proceso para que actúen de manejadores del propio proceso. Estas etiquetas son:

  • OnDeserialized: Se invoca cuando el objeto ya se ha deserializado.
  • OnDeserializing: Se invoca justo antes de deserializar el objeto.
  • OnSerialized: Se invoca cuando el objeto ya ha pasado por el proceso de serialización.
  • OnSerializing: Se invoca justo antes de serializar el objeto.

Debemos asociar posteriormente estas etiquetas a un método que nos sirva de manejador. Este método debe tener un único parámetro de tipo StreamingContext cuyo cometido es controlar el origen y destino de la secuencia de serialización. Dicho de otro modo más llano y campechano indica qué objeto es quien inicia la secuencia de datos (en este caso para la seriailzación/deserialización) a través de Context y con State indica cuál es el origen o destino de los datos. Esto último puede servir para especificar si es para un fichero, remoto, serializado, otra máquina, otro proceso, etc. Sabiendo esto, ya sólo nos falta escribir el método para deserializar:

   1: [OnDeserialized()]
   2: private void onDeserialize(StreamingContext context)
   3: {
   4:     this.Name = Encoding.Unicode.GetString(this.ByteName);
   5:     this.Description = Encoding.Unicode.GetString(this.ByteDescription);
   6: }

Para habilitar caracteres con tildes, ñ y otros, he usado la codificación de Unicode que si permite usarlos. Como se aprecia, se invoca una vez el objeto ya está serializado y se han leído los miembros (los campos ByteName y ByteDesription tendrás sus oportunos valores). Ahora vayamos a por la serialización, que invocaremos antes de serializar para preparar los arrays de bytes:

   1: [OnSerializing()]
   2: private void onSerialize(StreamingContext context)
   3: {
   4:     this.ByteName = Encoding.Unicode.GetBytes(this.Name);
   5:     this.ByteDescription = Encoding.Unicode.GetBytes(this.Description);
   6: }

Con este sencillo procedimiento no tendremos problemas de conversiones entre cualquier tipo de codificaciones.

No hay comentarios:

Publicar un comentario