Nota del autor

Si la entrada que estás leyendo carece de imágenes, no se ve el vídeo que teóricamente lleva incrustado o el código fuente mostrado aparece sin formato, podéis conocer los motivos aquí. Poco a poco iré restableciendo la normalidad en el blog.
Este blog es un archivo de los artículos situados previamente en Lobosoft.es y ha dejado de ser actualizado. Las nuevas entradas pueden encontrarse en www.lobosoft.es. Un saludo,
Lobosoft.

domingo, 17 de agosto de 2008

Serialización XML de un diccionario genérico en .NET

En el .Net Framework 2.0, los diccionarios genéricos no permiten, a priori, la serialización XML. Hace unos meses, trabajando en un proyecto que requería el uso de este tipo de colecciones para facilitar el paso de parámetros y la configuración del estado de unos objetos en Windows Workflow Foundation, me encontré con la “agradable” sorpresa de la falta de soporte en WF del uso de genéricos en las actividades personalizadas que podíamos desarrollar. Si bien un objeto de una determinada clase era configurable desde el editor de Workflow (en el propio IDE de Visual Studio), y permitía la ejecución del flujo de trabajo sin mayor problema, en cuanto pretendíamos incluir el uso de genéricos como una de las propiedades de la actividad en aras de obtener una mayor flexibilidad en la configuración de aquella, el Workflow perdía el estado del objeto cuando comenzaba su ejecución. Para resolver este impedimento (tan inexplicable cuando hablamos de la versión 3.5 del Framework), me vi obligado a implementar la serialización de las actividades personalizadas, así como del objeto diccionario que exponía como una propiedad inicializada en tiempo de diseño, y configurable para obtener la ejecución deseada de la actividad.


En la entrada de hoy presento una breve introducción a una posible implementación de la serialización XML de un diccionario genérico, aunque existen diversos enfoques para resolverla. De hecho, Scott Hanselman habla en su blog sobre la serialización de listas genéricas, en una entrada que recomiendo leer, ya que resulta complementaria a la mía.


Lo primero que debemos hacer es crear una clase etiquetada como serializable que heredará del diccionario genérico Dictionary, e implementará el interfaz IXmlSerializable [Líneas 11-14]. En mi caso, he nombrado a esta clase XDictionary (por eXtendedDictionary), e incluirá tres propiedades protected que permitirán la configuración de los nombres de las etiquetas XML para los campos del nombre del objeto, de la clave y el valor (por cada elemento incluido en nuestro diccionario a serializar). [Líneas 17-23]. Además del constructor sin parámetros, que da nombres concretos a las propiedades para una instancia de esta clase, incluyo un constructor que recibe dos parámetros, uno de tipo SerializationInfo y otro StreamingContext [Líneas 25-39]. De momento, y para la entrada de hoy, no será necesario entrar en ellos, ya que éste último es necesario fundamentalmente para su uso con WF. En cuanto a los miembros del interfaz IXmlSerializable, implementaremos obligatorialmente el método GetSchema, aunque simplemente devolveremos null, aunque podríamos implementar un esquema determinado para nuestro XML.


El meollo del asunto se encuentra en los métodos ReadXml y WriteXml. El primero de ellos recorre el XML insertando los valores encontrados en el mismo en un diccionario genérico, creando un par por cada nodo del XML que coincida con la configuración que especificábamos al inicializar el diccionario mediante sus propiedades protegidas. El valor es deserializado usando el deserializador del tipo correspondiente al valor –ya se sabe: el deserializador que lo deserialice, buen deserializador será-. Incluyo también una comprobación sobre la clave, ya que si existe previamente en el diccionario saltaría una excepción si intentamos insertarlo nuevamente. En este caso, sustituyo el valor previo por el encontrado posteriormente. Otras aproximaciones podrían pasar por ignorar este segundo encuentro, o por capturar la excepción y tratarla si llegase a producirse.


En cuanto al método WriteXml, su funcion es justo la contraria –o complementaria- a la de ReadXml. Se dedica a recorrer el diccionario genérico y a crear la estructura XML necesaria para albergarlo. Los valores son serializados usando el serializador propio del tipo que corresponda a aquellos.


El código podría usarse para crear una biblioteca de utilidad con clases abstractas, por ejemplo, que implementasen funcionalidades extendidas para las colecciones en .Net. En una futura entrada veremos cómo usarlo en combinación con WF para serializar un diccionario en tiempo de diseño y poder usarlo junto a esta novedosa tecnología. A continuación, por tanto, el código fuente de la entrada:




.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, "Courier New", Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #006080; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }


   1:  using System;

   2:  using System.Collections.Generic;

   3:  using System.Linq;

   4:  using System.Text;

   5:  using System.Xml.Serialization;

   6:  using System.Xml;

   7:  using System.Runtime.Serialization;

   8:   

   9:  namespace Lobosoft.Collections.Extended

  10:  {

  11:      [Serializable(),

  12:      XmlRoot("dictionary")]

  13:      public class XDictionary 

  14:          : Dictionary, IXmlSerializable

  15:      {

  16:          

  17:          #region Properties

  18:          

  19:          protected string ItemName { get; set; }

  20:          protected string KeyName { get; set; }

  21:          protected string ValueName { get; set; }

  22:          

  23:          #endregion

  24:   

  25:          #region Constructors

  26:          

  27:          public XDictionary()

  28:              : base()

  29:          {

  30:              Initialize();

  31:          }

  32:   

  33:          public XDictionary(SerializationInfo info, StreamingContext context)

  34:              : base(info, context)

  35:          {

  36:              Initialize();

  37:          }

  38:          

  39:          #endregion

  40:   

  41:          #region IXmlSerializable Members

  42:          /// 

  43:          /// Inicialmente, no tenemos un esquema XML para el diccionario

  44:          /// 

  45:          /// 

  46:          public System.Xml.Schema.XmlSchema GetSchema()

  47:          {

  48:              return null;

  49:          }

  50:   

  51:          /// 

  52:          /// Deserializa el XML dentro de un diccionario genérico, usando el XmlReader.

  53:          /// 

  54:          /// 

  55:          public void ReadXml(XmlReader reader)

  56:          {

  57:              XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));

  58:              XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

  59:   

  60:              bool wasEmpty = reader.IsEmptyElement;

  61:   

  62:              reader.Read();

  63:   

  64:              if (wasEmpty)

  65:                  return;

  66:   

  67:              reader.ReadStartElement(ItemName + "s");

  68:   

  69:              while ((reader.NodeType != XmlNodeType.EndElement) &&

  70:                     (reader.NodeType != XmlNodeType.None))

  71:              {

  72:                  reader.ReadStartElement(ItemName);

  73:                  reader.ReadStartElement(KeyName);

  74:                  TKey key = (TKey)keySerializer.Deserialize(reader);

  75:                  reader.ReadEndElement();

  76:   

  77:                  reader.ReadStartElement(ValueName);

  78:                  TValue value = (TValue)valueSerializer.Deserialize(reader);

  79:                  reader.ReadEndElement();

  80:   

  81:                  if (this.ContainsKey(key))

  82:                  {

  83:                      this[key] = value;

  84:                  }

  85:                  else

  86:                  {

  87:                      this.Add(key, value);

  88:                  }

  89:   

  90:                  reader.ReadEndElement();

  91:                  reader.MoveToContent();

  92:              }

  93:   

  94:              reader.ReadEndElement();

  95:   

  96:          }

  97:   

  98:          /// 

  99:          /// Serializa el diccionario, usando el XmlWriter que se le pase como argumento.

 100:          /// 

 101:          /// 

 102:          public void WriteXml(System.Xml.XmlWriter writer)

 103:          {

 104:              XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));

 105:              XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));

 106:   

 107:              writer.WriteStartElement(ItemName + "s");

 108:              foreach (TKey key in this.Keys)

 109:              {

 110:                  writer.WriteStartElement(ItemName);

 111:                  writer.WriteStartElement(KeyName);

 112:                  keySerializer.Serialize(writer, key);

 113:                  writer.WriteEndElement();

 114:   

 115:                  writer.WriteStartElement(ValueName);

 116:                  TValue value = (TValue)this[key];

 117:                  valueSerializer.Serialize(writer, value);

 118:                  writer.WriteEndElement();

 119:                  writer.WriteEndElement();

 120:              }

 121:              writer.WriteEndElement();

 122:          }

 123:          #endregion

 124:   

 125:          #region Methods

 126:   

 127:          private void Initialize()

 128:          {

 129:              ItemName = "myItemName";

 130:              KeyName = "myKeyName";

 131:              ValueName = "myValueName";

 132:          }

 133:   

 134:          #endregion

 135:      }

 136:  }

No hay comentarios:

Publicar un comentario