Tag Archives: C#

Cifrado y descifrado asimétrico con RSA utilizando C#/Mono

Introducción.

En criptografía, RSA (Rivest, Shamir y Adleman) es un sistema criptográfico de clave pública desarrollado en 1977. En la actualidad, RSA es el primer y más utilizado algoritmo de este tipo y es válido tanto para cifrar como para firmar digitalmente.

La seguridad de este algoritmo radica en el problema de la factorización de números enteros. Los mensajes enviados se representan mediante números, y el funcionamiento se basa en el producto, conocido, de dos números primos grandes elegidos al azar y mantenidos en secreto. Actualmente estos primos son del orden de 10200, y se prevé que su tamaño aumente con el aumento de la capacidad de cálculo de los ordenadores.

Como en todo sistema de clave pública, cada usuario posee dos claves de cifrado: una pública y otra privada. Cuando se quiere enviar un mensaje, el emisor busca la clave pública del receptor, cifra su mensaje con esa clave, y una vez que el mensaje cifrado llega al receptor, este se ocupa de descifrarlo usando su clave privada.

Se cree que RSA será seguro mientras no se conozcan formas rápidas de descomponer un número grande en producto de primos. La computación cuántica podría proveer de una solución a este problema de factorización.

Tomado del artículo RSA de Wikipedia.

Implementación.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

using System;
using System.IO;
using System.Security;
using System.Security.Cryptography;

Proveedor del servicio de cifrado RSA.

El núcleo de la implementación es el objeto RSACryptoServiceProvider que realiza las labores de cifrado/descifrado de información.

public static RSACryptoServiceProvider rsa;

Establecer los valores iniciales del servicio.

En este punto se establecen los valores iniciales de configuración del servicio, los cuales son en su mayoría opcionales, y se instancia el objeto mencionado anteriormente.

const string CONTAINER_NAME = "ContenedorRSA";

CspParameters cspParams;
cspParams = new CspParameters(1);	// PROV_RSA_FULL
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
cspParams.KeyContainerName = CONTAINER_NAME;

// Instanciar el algoritmo de cifrado RSA

rsa = new RSACryptoServiceProvider(cspParams);

Generar las llaves.

En esta etapa se crea una pareja de llaves: pública y privada, para las labores de cifrado.  Estas se almacenan por conveniencia en formato XML en los archivos llave_publica.xml y llave_privada.xml correspondientemente.

// Generar y almacenar la llave pública

writer = new StreamWriter(@"llave_publica.xml");
string publicOnlyKeyXML = rsa.ToXmlString(false);
writer.Write(publicOnlyKeyXML);
writer.Close();

// Generar y almacenar la llave privada

writer = new StreamWriter(@"llave_privada.xml");
string publicPrivateKeyXML = rsa.ToXmlString(true);
writer.Write(publicPrivateKeyXML);
writer.Close();

Cargar las llaves.

Una vez que han sido creadas y almacenadas las llaves, estas no deben ser creadas nuevamente.  Para su uso se cargan desde los correspondientes archivos XML y se asocian con el proveedor de cifrado.

// Cargar y asociar la llave pública al proveedor de cifrado

StreamReader reader = new StreamReader(@"llave_publica.xml");
string publicOnlyKeyXML = reader.ReadToEnd();
rsa.FromXmlString(publicOnlyKeyXML);
reader.Close();

// Cargar y asociar la llave privada (y pública) al proveedor de cifrado

StreamReader reader = new StreamReader(@"llave_privada.xml");
string publicPrivateKeyXML = reader.ReadToEnd();
rsa.FromXmlString(publicPrivateKeyXML);
reader.Close();

Cifrar la información.

Para realizar el cifrado de los datos es necesario contar con la llave pública del destinatario.

cargarLlavePublica();

// Convertir el texto a cifrar (plano) a su representación en bytse

byte[] textoPlanoBytes = System.Text.Encoding.UTF8.GetBytes(textoPlano);

// Realizar el proceso de cifrado

byte[] textoCifradoBytes = rsa.Encrypt(textoPlanoBytes, false);

// Convertir el mensaje cifrado a su representación en cadena

string MensajeCifrado = Convert.ToBase64String(textoCifradoBytes);

Descifrar la información.

Para descifrar los datos y obtener el mensaje original es necesario contar con la llave privada del destinatario del mensaje.

cargarLlavePrivada();

// Convertir el texto cifrado a su representación en bytse

byte[] textoCifradoBytes = Convert.FromBase64String(textoCifrado);

// Realizar el proceso de descifrado

byte[] textoPlanoBytes = rsa.Decrypt(textoCifradoBytes, false);

// Convertir el mensaje descifrado a su representación en cadena

string MensajeDescifrado = System.Text.Encoding.UTF8.GetString(textoPlanoBytes);

Aplicación de demostración.

Aplicación de demostración del uso del algoritmo
Aplicación de demostración del uso del algoritmo RSA

$ gmcs “/out:RSASample.exe” “/r:/usr/lib/mono/2.0/System.dll” /t:exe “RSASample/Main.cs”

Enlaces.

Cifrado y descifrado simétrico con Rijndael (AES) utilizando C#/Mono

Introducción.

Advanced Encryption Standard (AES), también conocido como Rijndael (pronunciado “Rain Doll” en inglés), es un esquema de cifrado por bloques adoptado como un estándar de cifrado por el gobierno de los Estados Unidos. El AES fue anunciado por el Instituto Nacional de Estándares y Tecnología (NIST) como FIPS PUB 197 de los Estados Unidos (FIPS 197) el 26 de noviembre de 2001 después de un proceso de estandarización que duró 5 años.  Se transformó en un estándar efectivo el 26 de mayo de 2002. Desde 2006, el AES es uno de los algoritmos más populares usados en criptografía simétrica.

El cifrador fue desarrollado por dos criptólogos belgas, Joan Daemen y Vincent Rijmen, ambos estudiantes de la Katholieke Universiteit Leuven, y enviado al proceso de selección AES bajo el nombre “Rijndael”.

Tomado del artículo Advanced Encryption Standard de Wikipedia.

Implementación con strings.

La aplicación de demostración de esta técnica requiere del uso de por lo menos los siguientes namespaces.

using System;
using System.Security.Cryptography;
using System.Text;
using System.IO;

Establecer la clave y el vector de inicio.

Estos valores pueden ser especificados manualmente o de manera automática por el framework.  La implementación para permitir que se definan automáticamente estos valores es la siguiente.

Rijndael rijndael = Rijndael.Create();
byte[] key = rijndael.Key;
byte[] iv  = rijndael.IV;

Es posible forzar la generación de nuevas claves y nuevos vectores de inicio para el algoritmo utilizando los métodos rijndael.GenerateKey() y rijndael.GenerateIV() respectivamente.

Si por el contrario se desea especificar estos valores manualmente su implementación es la siguiente siendo strKey y strIv, la clave y el vector de inicialización como cadenas de texto.

byte[] key = UTF8Encoding.UTF8.GetBytes(strKey);
byte[] iv  = UTF8Encoding.UTF8.GetBytes(strIv);

Especificando estos valores manualmente es necesario garantizar que sus longitudes sean válidas para el algoritmo.  En este caso se utilizará una longitud de clave de 32 bits y una longitud de vector de inicio de 16 bits.

int keySize = 32;
int ivSize = 16;

Array.Resize(ref key, keySize);
Array.Resize(ref iv, ivSize);

Cifrado de cadenas de texto.

Para cifrar la información se requiere de los siguientes parámetros.

  1. Cadena de texto con los datos a cifrar.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos cifrados.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	string		        texto cifrado
 */

public static string encryptString(String plainMessage, byte[] Key, byte[] IV)
{
    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Establecer un flujo en memoria para el cifrado

    MemoryStream memoryStream = new MemoryStream();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Obtener la representación en bytes de la información a cifrar

    byte[] plainMessageBytes = UTF8Encoding.UTF8.GetBytes(plainMessage);

    // Cifrar los datos enviándolos al flujo de cifrado

    cryptoStream.Write(plainMessageBytes, 0, plainMessageBytes.Length);

    cryptoStream.FlushFinalBlock();

    // Obtener los datos datos cifrados como un arreglo de bytes

    byte[] cipherMessageBytes = memoryStream.ToArray();

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos cifrados

    return Convert.ToBase64String(cipherMessageBytes);
}

Descifrado de cadenas de texto.

El proceso inverso, el de descifrado, se realiza de manera antagónica.  Para hacerlo es necesario contar con los siguientes parámetros.

  1. Cadena de texto con los datos cifrados.
  2. Clave.
  3. Vector de inicio.

El proceso retornará finalmente una cadena de texto con los datos descifrados.

/**
 * Descifra una cadena texto con el algoritmo de Rijndael
 *
 * @param	encryptedMessage	mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			texto descifrado (plano)
 */

public static string decryptString(String encryptedMessage, byte[] Key, byte[] IV)
{
    // Obtener la representación en bytes del texto cifrado

    byte[] cipherTextBytes = Convert.FromBase64String(encryptedMessage);

    // Crear un arreglo de bytes para almacenar los datos descifrados

    byte[] plainTextBytes = new byte[cipherTextBytes.Length];

    // Crear una instancia del algoritmo de Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo en memoria con la representación de bytes de la información cifrada

    MemoryStream memoryStream = new MemoryStream(cipherTextBytes);

    // Crear un flujo de descifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(memoryStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Obtener los datos descifrados obteniéndolos del flujo de descifrado

    int decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);

    // Cerrar los flujos utilizados

    memoryStream.Close();
    cryptoStream.Close();

    // Retornar la representación de texto de los datos descifrados

    return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}

Implementación con archivos.

El cifrado y descifrado de mensajes en archivos se realiza de manera similar al expuesto anteriormente con cadenas, sin embargo varían los flujos (streams) utilizados para obtener y dirigir el flujo de la información.

Cifrado a archivos.

/**
 * Cifra una cadena texto con el algoritmo de Rijndael y lo almacena en un archivo
 *
 * @param	plainMessage	mensaje plano (sin cifrar)
 * @param	filename	        nombre del archivo donde se almacenará el mensaje cifrado
 * @param	Key		        clave del cifrado para Rijndael
 * @param	IV		        vector de inicio para Rijndael
 * @return	void
 */

public static void encryptToFile(String plainMessage, String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateEncryptor(Key, IV),
                                                 CryptoStreamMode.Write);

    // Crear un flujo de escritura basado en el flujo de cifrado

    StreamWriter streamWriter = new StreamWriter(cryptoStream);

    // Cifrar el mensaje a través del flujo de escritura

    streamWriter.WriteLine(plainMessage);

    // Cerrar los flujos utilizados

    streamWriter.Close();
    cryptoStream.Close();
    fileStream.Close();
}

Descifrado de archivos.

/**
 * Descifra el contenido de un archivo con el algoritmo de Rijndael y lo retorna
 * como una cadena de texto plano
 *
 * @param	filename		nombre del archivo donde se encuentra el mensaje cifrado
 * @param	Key			clave del cifrado para Rijndael
 * @param	IV			vector de inicio para Rijndael
 * @return	string			mensaje descifrado (plano)
 */

public static string decryptFromFile(String filename, byte[] Key, byte[] IV)
{
    // Crear un flujo para el archivo a generarse

    FileStream fileStream = File.Open(filename, FileMode.OpenOrCreate);

    // Crear una instancia del algoritmo Rijndael

    Rijndael RijndaelAlg = Rijndael.Create();

    // Crear un flujo de cifrado basado en el flujo de los datos

    CryptoStream cryptoStream = new CryptoStream(fileStream,
                                                 RijndaelAlg.CreateDecryptor(Key, IV),
                                                 CryptoStreamMode.Read);

    // Crear un flujo de lectura basado en el flujo de cifrado

    StreamReader streamReader = new StreamReader(cryptoStream);

    // Descifrar el mensaje a través del flujo de lectura

    string plainMessage = streamReader.ReadLine();

    // Cerrar los flujos utilizados

    streamReader.Close();
    cryptoStream.Close();
    fileStream.Close();

    return plainMessage;
}

Aplicación de demostración.

La aplicación de demostración incluye los conceptos y el código expuestos en este artículo.  Con ella es posible cifrar y descifrar un mensaje que consiste en una cadena de texto arbitraria en memoria y en un archivo.

Aplicación de demostración del uso del algoritmo Rijndael
Aplicación de demostración del uso del algoritmo Rijndael

Construír la aplicación.

La aplicación de demostración puede construírse utilizando la solución incluída en la distribución con MonoDevelop o Visual Studio.  También es posible construírla desde línea de comando (Mono) mediante la siguiente instrucción.

$ gmcs “/out:RijndaelSample.exe” “/r:/usr/lib/mono/2.0/System.dll” /t:exe “RijndaelSample/Main.cs”

Enlaces.

Calculando hashes (MD5 y SHA1) con C#/Mono

Introducción.

Una función de hashing recibe un bloque de datos arbitrarios y genera de manera determinística un arreglo de bytes con el valor de su hash, el cual tiene como característica el ser único para los datos procesados.

Los valores de hash tienen múltiples usos, entre ellos se utilizan en la criptografía para garantizar que la información de un mensaje no ha sido modificada.  Con este mismo uso, comúnmente se utiliza para determinar si la descarga de archivos ha sido exitosa o no (md5sum).

Existen varios algortimos de hashing, los mas conocidos son MD5 de 128 bits y SHA1 de 160 bits.  El primero de ellos es muy utilizado actualmente, especialmente para el almacenamiento de contraseñas, sin embargo ya no se considera seguro por lo cual es conveniente utilizar el segundo mencionado.

Implementación en C#/Mono.

Para la implementaciones que se describen a continuación se utilizaron los siguientes namespaces.

using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;

Calcular el hash de una cadena.

El primer paso es crear instancias de los algoritmos de hashing.

HashAlgorithm md5  = HashAlgorithm.Create("MD5");
HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");

Posteriormente se calcula el valor del hash de la cadena texto (text) especificada.

Byte[] md5Data  = md5.ComputeHash(UTF8Encoding.UTF8.GetBytes(text));
Byte[] sha1Data = sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(text));

Finalmente su valor puede ser manipulado o entregado al usuario.

Console.WriteLine("MD5:  " + BitConverter.ToString(md5Data).Replace("-", ""));
Console.WriteLine("SHA1: " + BitConverter.ToString(sha1Data).Replace("-", ""));

Calcular el hash de un archivo.

El procedimiento es muy similar al cálculo de hash de una cadena, pero a diferencia de este, los datos provienen de un flujo de archivo al cual se le calcula su valor.

FileStream fs = File.OpenRead(filename);

md5Data  = md5.ComputeHash(fs);
sha1Data = sha1.ComputeHash(fs);

fs.Close ();

Georreferenciando de manera fácil y simple con GeoNames utilizando C#/Mono

Introducción.

Últimamente he tenido ganas de desarrollar en C# para retomar este lenguaje que tengo un poco abandonado y al mismo tiempo probar los avances que ha tenido el proyecto Mono.  Que mejor inicio que desarrollar una aplicación muy simple para georreferenciar ciudades aprovechando el servicio web de Geonames, esta a su vez me servirá para complementarla y solucionar un problema de información que tengo para otro proyecto.

Como se podrá apreciar, el código es muy sencillo ya que no utilizo ninguna librería adicional a las estándar, sin embargo es interesante y puede serle de utilidad a alguien mas.

Cuál es la necesidad ?

El problema que se tiene es la necesidad de georreferenciar ubicaciones, es decir, conociendo su nombre obtener su ubicación en términos de latitud y longitud.

Si estoy en Manizales, Caldas, Colombia estaré realmente en latitud 5.07, longitud -75.52056.

Es necesario tener en cuenta que muchas veces el servicio de georreferenciación retorna múltiples resultados ya que pueden haberse referenciado varios sitios con el mismo nombre, haberse cambiado el nombre de la ubicación o tenerse varios hitos de diferente nivel administrativo.  En Geonames esto último se identifica mediante los campos de características.  Consulte el sitio web de Geonames para mas información acerca de los feature codes.

Cómo es el servicio web ?

El servicio web de búsquedas de Geonames recibe una cadena de texto con la ubicación que se desea georreferenciar y retorna una cadena de texto en formato XML (en este caso) con los resultados obtenidos.

Por ejemplo, la consulta de Manizales, Caldas, Colombia se realizaría a través del siguiente URL.

http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=Manizales,Caldas,Colombia

Retornará el siguiente resultado.

<geonames style="FULL">
    <totalResultsCount>2</totalResultsCount>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.07</lat>
        <lng>-75.52056</lng>
        <geonameId>3675443</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>P</fcl>
        <fcode>PPLA</fcode>
        <fclName>city, village,...</fclName>
        <fcodeName>seat of a first-order administrative division</fcodeName>
        <population>357814</population>
        <alternateNames>Manisales,Manisalesas,Manizales,Манисалес</alternateNames>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2/>
        <adminName2/>
        <alternateName lang="en">Manizales</alternateName>
        <alternateName lang="pl">Manizales</alternateName>
        <alternateName lang="lt">Manisalesas</alternateName>
        <alternateName lang="bg">Манисалес</alternateName>
        <alternateName lang="cs">Manizales</alternateName>
        <alternateName lang="link">http://en.wikipedia.org/wiki/Manizales</alternateName>
        <alternateName lang="de">Manizales</alternateName>
        <alternateName lang="sv">Manizales</alternateName>
        <alternateName lang="es">Manizales</alternateName>
        <alternateName lang="no">Manizales</alternateName>
        <alternateName lang="pt">Manizales</alternateName>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>1.0</score>
    </geoname>
    <geoname>
        <toponymName>Manizales</toponymName>
        <name>Manizales</name>
        <lat>5.08333</lat>
        <lng>-75.5</lng>
        <geonameId>3675444</geonameId>
        <countryCode>CO</countryCode>
        <countryName>Colombia</countryName>
        <fcl>A</fcl>
        <fcode>ADM2</fcode>
        <fclName>country, state, region,...</fclName>
        <fcodeName>second-order administrative division</fcodeName>
        <population/>
        <alternateNames/>
        <elevation/>
        <continentCode>SA</continentCode>
        <adminCode1>37</adminCode1>
        <adminName1>Caldas</adminName1>
        <adminCode2>3675444</adminCode2>
        <adminName2>Manizales</adminName2>
        <timezone dstOffset="-5.0" gmtOffset="-5.0">America/Bogota</timezone>
        <score>0.3448537290096283</score>
    </geoname>
</geonames>

Finalmente la tarea de la aplicación que consuma el servicio será procesar el documento XML y obtener la información que necesite.

Cómo es la implementación ?

Las librerías (namespaces) utilizados son los siguientes.

using System;
using System.Net;
using System.Text;
using System.Xml;
using System.Collections.Generic;

Se prepara el URL para consumir el servicio con la ubicación a geocodificar.  En este caso, especifico una por defecto si no suministra una.

string location = (args.Length == 0) ? "Manizales, Caldas, Colombia" : args[0];
string url = "http://ws.geonames.org/search?lang=es&type=xml&style=FULL&q=" + location;

Se consume el servicio web y se obtiene la cadena de texto con la respuesta en formato XML.

WebClient client = new WebClient();
string xml = client.DownloadString(url);

Se crea un objeto para manipular el documento XML recién recibido, se obtienen los nodos relacionados con la respuesta de la georreferenciación (geoname)  y se verifica que haya registros, de lo contrario no habrá nada que hacer.

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

XmlNodeList geonames = doc.GetElementsByTagName("geoname");

if(geonames.Count == 0)
 throw new Exception("There is no geonames in the result!");
else
 Console.WriteLine("I found " + geonames.Count + " places!n");

Se recorren los resultados obtenidos extrayéndoles la información georreferenciada que se requiera.

foreach(XmlElement place in geonames)
{
 Console.WriteLine("* LUGAR: " + place["name"].InnerText + "n");

 Console.WriteLine("tNombre: "          + place["name"].InnerText);
 Console.WriteLine("tDepartamento: "    + place["adminName1"].InnerText);
 Console.WriteLine("tLatitud: "         + place["lat"].InnerText);
 Console.WriteLine("tLongitud: "        + place["lng"].InnerText);
 Console.WriteLine("tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("tPoblación: "       + place["population"].InnerText + " habitantes");
 Console.WriteLine("tPaís: "            + place["countryName"].InnerText +
                   " (" + place["countryCode"].InnerText + ")");
 Console.WriteLine("tCaracterísticas: " + place["fcl"].InnerText + "/" + place["fcode"].InnerText +
                   "> " + place["fclName"].InnerText + " (" + place["fcodeName"].InnerText + ")");

 Console.WriteLine();
}

Cómo construyo el programa ?

Si se utiliza VisualStudio/MonoDevelop sólo es necesario abrir la solución (incluída en el paquete de demostración) y ejecutar el comando de Build o Run del menú.

Si se desea realizar manualmente deberá ejecutarse un comando como el siguiente.

$ gmcs “/out:GeoCoderSimple.exe” “/r:/usr/lib/mono/2.0/System.dll” “/r:/usr/lib/mono/2.0/System.Xml.dll” /t:exe “geonames_search_raw/Main.cs

Donde geonames_search_raw/Main.cs es el código fuente de la aplicación y GeoCoderSimple.exe será el archivo binario resultante.

Cómo ejecuto el ejemplo ?

Ya sea invocándolo desde el IDE o desde línea de comando, el programa recibe un argumento.  En caso de obviarse este argumento se utilizará el valor por defecto.

$ ./GeoCoderSimple.exe

I found 2 places!

* LUGAR: Manizales

 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.07
 Longitud: -75.52056
 País: Colombia (CO)
 Población: 357814 habitantes
 País: Colombia (CO)
 Características: P/PPLA> city, village,... (seat of a first-order administrative division)

* LUGAR: Manizales

 Nombre: Manizales
 Departamento: Caldas
 Latitud: 5.08333
 Longitud: -75.5
 País: Colombia (CO)
 Población:  habitantes
 País: Colombia (CO)
 Características: A/ADM2> country, state, region,... (second-order administrative division)

Enlaces.

Utilizando el Wiimote con C#: parte II

HelloWii 0.1 screenshot
HelloWii 0.1 screenshot

Introducción.

Antes de publicar el código fuente del HelloWii, decidí hacer un poco de refactory obteniendo los siguientes productos y conclusiones.

  • La clase WiiController que es un recubrimiento (wrapper) de la librería WiimoteLib y que simplifica su acceso.  Está desarrollada en C#.
  • Se utilizó el patrón observador (interfaz WiiControllerObserver) para permitirle a los interesados registrarse con el control para ser notificados de sus cambios de estado.  De igual manera es posible obtener información de este por demanda.
  • Si el observador es el mismo componente de UI (Form por ejemplo) es recomendable remover el registro del observador (unregisterObserver) antes de cerrar/destruír la componente/aplicación y evitar así excepciones generadas durante intentos de actualización mientras se cierra el componente.
  • Se adaptó la aplicación de demostración para utilizar a un objeto WiiController como interfaz en lugar de utilizar a la librería directamente.
  • Con el rediseño se esperaba evitar el problema del cross-threading pero no fue posible.

Como se mencionó no fue posible obviar la situación generada por la ejecución de instrucciones entre distintos hilos: la interfaz de usuario y la librería de acceso al wiimote que genera los problemas y explicación ya fueron expuestos.  Sin embargo, revisando el foro de WiimoteProject encontré que es posible indicarle al componente visual de .NET que se encuentre accediendo a la librería (inclusive indirectamente a través de WiiController) que ignore este tipo de accesos ilegales entre hilo al evitando su revisión, esto se logra agregando la siguiente instrucción al constructor del componente.

CheckForIllegalCrossThreadCalls = false;

API de la clase WiiController.

La clase WiiController cuenta con los siguientes métodos.

  • public void connect() – Creates the conexion with the wiimote.
  • public void disconnect() – Makes the disconnection of the wiimote..
  • public bool isConnected() – Checks if the wiimote has been already connected..
  • public void refreshStatus(WiimoteState source) – Updates the references to the internal information of the wiimote.
  • public void setReportType(WiimoteLib.InputReport type) – Selects the the type of information report that is expected from the controller.
  • public float getBattery() – Gets the battery level of the wiimote.
  • public String getId() – Gets the ID of the controller.
  • public bool[] getAllButtonsStatus() – State of all buttons of the wiimote.
  • public bool getButtonStatus(String name) – The status of a button on the wiimote.
  • public bool[] getAllLedsStatus() – The status of all leds of the wiimote.
  • public bool getLedStatus(int id) – The status of an individual led.
  • public void setLeds(bool l1, bool l2, bool l3, bool l4) – Change the state of the wiimote’s leds.
  • public void setLeds(bool[] leds) – Change the state of the wiimote’s leds.
  • public void turnLed(int index, bool status) – Change the state of an individual led.
  • public int[] getAcceleration() – Obtains the acceleration of the wiimote.
  • public int getAcceleration(String axis) – Obtains the acceleration of the wiimote.
  • public bool isRumbling() – Checks if the rumbling device is active.
  • public void rumble(bool status) – Change the state of the wiimote’s rumbling device.
  • public bool isObserver(WiiControllerObserver observer) – Checks if an observer is already registered.
  • public bool registerObserver(WiiControllerObserver observer) – Register a new observer on the controller.
  • public bool unregisterObserver(WiiControllerObserver observer) – Unregister an observer from the controller.
  • public bool isExtensionConnected() – Checks if a wiimote’s extension is connected.
  • public String getExtensionTypeConnected() – Get the type name of the connected extension.
  • public int[] getNunchukAcceleration() – Get the acceleration of the nunchuk extension.
  • public int getNunchukAcceleration(String axis) – Get the acceleration of the nunchuk extension.
  • public bool[] getAllNunchukButtonsStatus() – Get the state of the buttons on the nunchuk extension.
  • public bool getNunchukButtonStatus(String name) – Get the state of a button on the nunchuk extension.
  • public int[] getNunchukJoystick() – Get the joystick state on the nunchuk extension.
  • public int getNunchukJoystick(String axis) – Get the joystick value on a selected axis from the nunchuk extension.

Para mas información acerca del API de la clase y de su respectiva utilización consulte la documentación del código fuente inmersa en el archivo WiiController.cs y en WiiForm.cs (aplicación de demostración).

Registrar observadores.

Para registrarse como observador de eventos del wiimote la clase debe implementar la interfaz jimezam.wii.controller.WiiControllerObserver.  Esta interfaz obliga a que se implementen los siguientes métodos.

  • void update() – Invoked when the status of the wiimote is changed.
  • void updateExtension() -Invoked when an extension is connected/disconnected to the wiimote.

La manipulación del registro de un observador se realiza utilizando los métodos registerObserver y unregisterObserver de la clase WiiController descritos anteriormente.

Enlaces.

Selección de Debug/Release en Visual C# Express 2008

Esta semana, gracias a las pruebas con el Wiimote he vuelto a trabajar con C#, esta vez con utilizando el Visual C# Express 2008.  Encontré un problema, cuando quise generar la versión final de la aplicación no pude encontrar la opción que me permitiera elegir entre las presentaciones Debug/Release del ejecutable generado.

Buscando en Internet encontré que hay dos formas de hacerlo.

  1. Presionando F5 se construye el ejecutable en modo Debug, presionando CTRL+F5 se construye el ejecutable en modo Release.
  2. Si realmente se desea tener visible el menú que le permita seleccionar el modo del ejecutable y la CPU objetivo realice los siguientes pasos.
  • Elija el menú Tools > Options.
  • Seleccione la casilla de verificación Show all settings en la parte inferior izquierda.
  • Seleccione la rama Projects and solutions > General.
  • Selecciona la casilla de verificación Show advanced build configurations.
  • Presione el botón OK para aceptar los cambios.

debug-release-bar

Utilizando el Wiimote con C#

Ejes del Wiimote - tomado de http://wiimotecommande.sf.net/
Ejes del Wiimote - tomado de http://wiimotecommande.sf.net/

Introducción.

Hace unos dias me prestaron un control de Wii (que si funcionaba) y tuve un par de noches para jugar con el.  Hice una aplicación muy sencilla para experimentar como acceder a la información provista por el control y como utilizar algunos de sus recursos.

La aplicación de demostración se realizó utilizando la librería WiimoteLib de Brian Peek y Visual C# Express bajo Windows Vista (utilizando la pila de Bluetooth del sistema operativo).

Implementación.

Instalación de la librería.

  • Descargar WiimoteLib (se utilizó la versión 1.6) desde su repositorio en CodePlex.
  • Se debe descomprimir el paquete y ubicar el archivo WiimoteLib.dll en un sitio conocido.
  • Al proyecto o solución en Visual Studio se le debe agregar la referencia a este archivo.
    • En el explorador de soluciones hacer clic derecho sobre el proyecto.
    • Seleccionar la opción Agregar referencia ….
    • Seleccionar la etiqueta Examinar.
    • Buscar el archivo Wiimote.dll y presionar el botón Aceptar.

Inclusión del espacio de nombres.

Para utilizar cualquiera de las clases de la librería dinámica es necesario incluír en el código fuente al espacio de nombres apropiado.

using WiimoteLib;

Referencia al Wiimote.

El acceso a la información del control se realizará a través de esta referencia, motivo por el cual la utilizo como atributo de la instancia.

private Wiimote wm;

Instanciación de referencia con el Wiimote.

Para que la creación de la instancia y posterior conexión sean exitosas es necesario que el control se encuentre ya asociado al equipo a través del emparentamiento Bluetooth.  El procedimiento para realizar esta conexión puede consultarse en este sitio (video) utilizando las herramientas nativas de Windows Vista o en este otro sitio utilizando la pila de Bluesoleil.

            try
            {
                wm = new Wiimote();
            }
            catch (System.IO.IOException ioe)
            {
                Console.WriteLine("No es posible ejecutar la aplicación: " + ioe.Message);
            }

Registro de eventos.

Se establecen métodos para manejar dos tipos de eventos del control:

  • Eventos generados por el control mismo: wm_WiimoteChanged.
  • Eventos generados por la conexión/desconexión de extensiones: wm_WiimoteExtensionChanged.
            wm.WiimoteChanged += wm_WiimoteChanged;
            wm.WiimoteExtensionChanged += wm_WiimoteExtensionChanged;

Establecimiento de la conexión.

            try
            {
                wm.Connect();
            }
            catch (Exception wnfe)
            {
                Console.WriteLine("No es posible ejecutar la aplicación: " + wnfe.Message);
            }

Establecimiento del nivel de reporte.

Determina que tipo de información (informes) estamos interesados en tomar del Wiimote.

            wm.SetReportType(InputReport.IRExtensionAccel, true);

Los diferentes tipos de reportes son las siguientes constantes definidas en InputReport.

  • Status.  Estado general.
  • ReadData. Datos de la memoria interna.
  • Buttons. Botones pulsados
  • ButtonsAccel. Botones y acelerómetro.
  • IRAccel. Botones, acelerómetro e infrarrojo.
  • ButtonsExtension. Botones y extensiones conectadas (como el Nunchuk, el control convencional, la guitarra, etc.)
  • ExtensionAccel. Botones, acelerómetro y extensiones.
  • IRExtensionAccel. Botones, acelerómetro, infrarrojos y extensiones.

Manejo de eventos en conexión de extensiones.

El método wm_WiimoteExtensionChanged es invocado cuando suceden cambios de conexión entre el Wiimote y sus extensiones.  La verificación de conexión se realiza con la propiedad args.Inserted.

        void wm_WiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs args)
        {
            if (args.Inserted)
                wm.SetReportType(InputReport.IRExtensionAccel, true);    // return extension data
            else
                wm.SetReportType(InputReport.IRAccel, true);             // back to original mode
        }

Manejo de eventos ante cambios en el Wiimote.

El método wm_WiimoteChanged es invocado cuando sucede un cambio en las variables del control (el acelerómetro detectó movimiento, se presionó un botón, etc.).

        void wm_WiimoteChanged(object sender, WiimoteChangedEventArgs args)
        {
              // ...
        }

Este es el punto ideal para realizar las consultas actualizadas acerca del estado del control.

Consulta del estado del control.

La información del estdo del control se almacena entre diferentes propieades del Wiimote.

  • Estado general del Wiimote.
  • Estado de los botones.
  • Estado de los leds.
  • Estado del acelerómetro.
WiimoteState ws = args.WiimoteState;
WiimoteLib.ButtonState bs = ws.ButtonState;
LEDState ls = ws.LEDState;
AccelState at = ws.AccelState;

Consulta de la batería.

int battery = (int) ws.Battery;

Consulta del estado de los botones del Wiimote.

            bool[] bStatus =
            {
                bs.Up, bs.Down, bs.Left, bs.Right,
                bs.A, bs.B,
                bs.Minus, bs.Home, bs.Plus,
                bs.One, bs.Two
            };

Cada una de las constantes corresponde con los botones etiquetados de igual manera en el control:

  • Control de dirección: Up, Down, Left, Right.
  • Botones superiores: A y B.
  • Botones medios: Minus (-), Home (casita) y Plus (+).
  • Botones inferiores: One (1) y Two (2).

Las propiedades son booleanas y serán true si el botón se encuentra presionado, false de lo contrario.

Manipulación del estado de los leds.

Su información se puede acceder de manera similar a lo realizado anteriormente con los botones.

            bLed = new bool[4]
            {
                ls.LED1, ls.LED2, ls.LED3, ls.LED4
            };

El dispositivo cuenta con cuatro leds.  Su estado puede modificarse de la siguiente manera.

            wm.SetLEDs(bLed[0], bLed[1], bLed[2], bLed[3]);

Donde cada parámetro corresponde a cada uno de los leds y el valor enviado deberá ser true para encenderlo o false para apagarlo.

Información del acelerómetro.

Como su nombre lo indica, provee información acerca de la aceleración del movimiento del control en sus tres ejes (consultar gráfica inicial).  Sus valores son enteros y pertenecen al rango entre 0 y 255.

            int rx = (int)at.RawValues.X;
            int ry = (int)at.RawValues.Y;
            int rz = (int)at.RawValues.Z;

Manipulación del vibrador.

Con la siguiente instrucción es posible verificar si el dispositivo vibrador del control se encuentra activo o no, retorna true o false según corresponda.

            wm.WiimoteState.Rumble;

De igual manera es posible encender o apagar al dispositivo vibrador según se requiera, esto se realiza de la siguiente manera.

            wm.SetRumble(state);

Donde state es true o false según se desee encender o apagar.

Información de las extensiones – Nunchuk.

Verificar si se encuentra o no alguna extensión conectada al Wiimote.

            bool ext = ws.Extension;

Verificar si la extensión conectada es el Nunchuk.

            if (ws.ExtensionType.ToString() == "Nunchuk")
            {
                 ...
            }

Estado del joystick del Nunchuk.

            WiimoteLib.Point joystick = ws.NunchukState.RawJoystick;
            int joyX = joystick.X;
            int joyY = joystick.Y;

Estado de los botones del Nunchuk.

            bool bCn = ws.NunchukState.C;
            bool bZn = ws.NunchukState.Z;

Información del acelerómetro del Nunchuk.

            Point3 nAccel = ws.NunchukState.AccelState.RawValues;
            int rnx = nAccel.X;
            int rny = nAccel.Y
            int rnz = nAccel.Z;

De manera similar a la información acelerómetro del Wiimote, sus valores van entre 0 y 255.

Problemas con el acceso entre hilos de C#.

En la implementación de la aplicación de demostración me encontré con un problema del lenguaje C#/.NET.  Al parecer cuando se ejecuta el código de la librería (código nativo me imagino) se ejecuta (por seguridad, me imagino nuevamente) en un hilo diferente, así que cuando intentaba realizar una actualización de la interfaz de usuario a partir de un valor obtenido del Wiimote se lanzaba una excepción.

Con este problema la siguiente instrucción, que actualiza el valor de un Label con la aceleración en X del Nunchuk es problemática.

           this.lnaXValue.Text = ws.NunchukState.AccelState.RawValues.X.ToString();

Por suerte encontré en Threading in Windows Forms una buena descripción del problema y la estrategia para encontrar una solución, no muy elegante para mi gusto, para solventar el problema por lo menos de manera temporal y continuar con el experimento del Wiimote.

Para ejecutar la instrucción problemática tuve que implementar un método actualizador de Labels y llamarlo a través de un delegado.

        delegate void updateLabelInformationDelegate(Label l, String value);

        public void updateLabelInformation(Label l, String value)
        {
            l.Text = value;
        }

La invocación se realiza entonces de la siguiente manera.

        if (InvokeRequired)
        {
             BeginInvoke(new updateLabelInformationDelegate(updateLabelInformation),
                         new object[] {
                                       this.lnaXValue,
                                       ws.NunchukState.AccelState.RawValues.X.ToString()
                         });
        }

Aplicación de demostración.

HelloWii 0.1
HelloWii 0.1

HelloWii 0.1 es la aplicación de prueba que implementé utilizando la librería y operaciones descritas anteriormente.  La funcionalidad de esta aplicación es la siguiente.

Wiimote.

  • Estado del d-pad.
  • Estado de los botones.
  • Estado de los leds.
  • Encender/apagar los leds haciendo clic sobre ellos.
  • Estado de la batería.
  • Número del HID.
  • Valores de aceleración en sus tres ejes.
  • Estado del vibrador.
  • Encender/apagar el vibrador seleccionado o removiendo la casilla de verificación.
  • Estado de la extensión: presente o ausente.

Nunchuk (si está presente).

  • Dirección del joystick.
  • Estado de los botones.
  • Valores de aceleración en sus tres ejes.

Esta aplicación y su código fuente pueden descargarse de manera libre.

Conclusiones.

La implementación de aplicaciones que interactúan con el Wiimote utilizando C#/WiimoteLib es muy sencillo, obviando el problema de comunicación entre hilos, no se presentó ningún inconveniente.  El API de la librería se encuentra bien documentado en un CHM que viene incluído en su distribución.

Habiendo probado ya su funcionamiento y documentado su implementación es necesario continuar con algunas pruebas un poco mas elaboradas y estructuras que en lo posible, eviten la necesidad de los delegados y permitan integrar su código con aplicaciones mas complejas y otras librerías de terceros.  También es necesario realizar pruebas con el lector infrarrojo del control y con el parlante, que según creo aún no tiene soporte por esta librería.

De igual manera planeo realizar pruebas similares utilizando Java.

Enlaces.

Para consultar el código fuente de la aplicación de demostración acceda al enlace de la segunda parte de este artículo.

Probando Firebird Embedded con C#

Desde hace mucho tiempo tengo planeado desarrollar una aplicación cuya versión 0.1 desarrollé hace ya varios años en C++ y Fox Toolkit, perdida ya en el museo del olvido.

La aplicación es muy sencilla.  Inicialmente son dos módulos pero la idea es agregarle después algunos otros.  El problema es que cada vez que me siento a pensar en ella termino haciendo un diseño complejo y lleno de cositas que me pondrían a estudiar.  Esto no sería malo sino fuera porque de esta manera se va a terminar cuando el tiempo tienda a infinito.

Por esto he cambiado mi plan.  Voy a hacer una aplicación pequeña y la voy a ir hacer creciendo, aunque esto signifique que alguna versión deba volverla a hacer desde scratch (cero).

Con respecto al lenguaje de programación había elegido Java para retomar mi estudio pero en medio de la moda local y temporal de .NET he decidido empezar a implementar en C#.

El primer reto a resolver es la escogencia de un motor de base de datos ya que la aplicación va a ser pequeña, me interesa que se pueda ejecutar en equipos no necesariamente robustos, no quiero despilfarrar recursos y quiero que la instalación y copia de seguridad sean fáciles: sólo copiar archivos.  Por esto he pensado en utilizar una base de datos basada en archivos o embedded.

Para Java hay varias de donde escoger, pero para .NET la oferta, al parecer, no es tan amplia.  Encontré SQLite (ya lo he utilizado varias veces pero he tenido problemas con bloqueos) VistaDB (comercial, no me sirve) y a SharpHSQL hermanita de HSQL (Java) para .NET, sin embargo no estoy seguro de cuan activo está ese proyecto.  Las mejores opciones por ahora parecen ser FireBird Embedded y SQL Server Compact, ambas prometen muchas cosas, incluyendo un bajo footprint ~ 2MB.

Para la primera prueba elegí a FireBird ya que esa fue la primera base de datos que utilicé en un proyecto después de graduado como ingeniero y me unen a ella emotivos recuerdos :-)

Instalación.

Para desarrollar una aplicación con FireBird inmerso se requieren dos recursos.

El FirebirdClient se instala como cualquier aplicación Windows sin ninguna opción específica.  Me imagino que habrá que instalarlo también en los clientes cuando se haga el despliegue (por confirmar).

La distribución se debe descomprimir y mover los siguientes archivo al mismo directorio donde se encontrará el archivo ejecutable de la aplicación: <ruta>binDebug (desarrollo) o <ruta>binRelease (producción).

  • fbembed.dll
  • icudt30.dll
  • icuin30.dll
  • icuuc30.dll
  • firebird.conf
  • firebird.msg

Para la implementación del proyecto estoy utilizando Visual C# Express 2008 y en él es necesario incluír la referencia a FirebirdSql.Data.FirebirdClient instalada con el Data Provider.

Creación de la cadena de conexión.

Es fácil construír la ConnectionString con la ayuda del FbConnectionStringBuilder de la siguiente manera.

            FbConnectionStringBuilder csb = new FbConnectionStringBuilder();

            csb.ServerType = FbServerType.Embedded;
            csb.UserID     = "SYSDBA";
            csb.Password   = "masterkey";
            csb.Dialect    = 3;
            csb.Database   = @"datadatabase.fdb";
            csb.Charset    = "UTF8";

Sólo los parámetros ServerType y Database son obligatorios, los demás son opcionales y puede encontrarse mayor información sobre ellos en ConectionStringParameters, también se puede encontrar mayor información acerca de los Charset disponibles en Firebird Character Sets and Collations.

La ubicación de la base de datos (parámetro Database) es relativo a la ubicación del archivo fembed.dll, es decir, al ejecutable de la aplicación.

Creación de la base de datos.

Desde la misma aplicación es posible crear la base de datos (manera programática) a la cual hace referencia la cadena de conexión.

           FbConnection.CreateDatabase(csb.ToString());

Debe tenerse en cuenta que la ruta de directorios bajo la cual se ubicará la base de datos ya debe existir previa la creación del archivo de datos, de lo contrario la creación fallará.

Conexión a la base de datos.

          con = new FbConnection(csb.ToString());
          con.Open();

Verificación del estado de la conexión.

            if (con.State == System.Data.ConnectionState.Open)
                Console.WriteLine("Established");
            else
                Console.WriteLine("NOT established");

Otros posibles estados definidos por ConnectionState son Broken, Closed, Connecting, Executing y Fetching.

Verificación de la existencia de una tabla.

            FbCommand cmd = new FbCommand("SELECT COUNT(RDB$RELATION_NAME)" +
                                          "FROM RDB$RELATIONS WHERE (RDB$RELATION_NAME = 'users')" +
                                          "AND RDB$VIEW_SOURCE IS NULL", con);

            int tableCount = (int)cmd.ExecuteScalar();

            if (tableCount == 0)
                Console.WriteLine("No");
            else
                Console.WriteLine("Yes");

Verifica la existencia o no de la tabla users en la base de datos conectada.

Ejecución de un INSERT.

            String sql = "INSERT INTO "users" VALUES ('" + user[0] + "', '" + user[1] + "')";
            cmd = new FbCommand(sql, con);
            cmd.ExecuteNonQuery();

Ejecución de una consulta de datos.

            String select = "SELECT username, name FROM "users"";
            cmd = new FbCommand(select, con);
            FbDataReader reader = cmd.ExecuteReader();

            try
            {
                while (reader.Read())
                {
                    String username = reader.GetString(0).Trim();
                    String name = reader.GetString(1).Trim();

                    Console.WriteLine("t{'" + username + "', '" + name + "'}");
                }
            }
            finally
            {
                reader.Close();
            }

Finalización de la conexión.

            con.Close();

Conclusiones.

Esta primera aproximación fue sencilla, práctica y funcional.  Se encuentra buena documentación del motor de base de datos y movimiento en los foros en su respecto.  A pesar de que, al menos por estas latitudes, no es muy utilizada Firebird parece ser que en otras partes si lo es.

Los archivos requeridos (dlls) ocupan 2.87MB mientras que todos los archivos, incluyendo los opcionales, ocupan 4.94MB.

Voy a revisar también cuan viable es utilizar el SQL Server Compact, sin embargo en los últimos días que hemos tenido un poco de contacto con SQL Server han aparecido con él algunos detalles desagradables que desde mi punto de vista de conocer poco acerca de este motor, me desaniman de utilizarlo.  Ya veremos.

Enlace: Aplicación de prueba – Firebird Embedded Demo 0.1.

Serialización de objetos en XML con C#.NET

De manera análoga a como en Java es posible realizar la serialización de clases a XML, encontré que también es posible hacerlo en C#.NET.  Para esto es necesario que la clase a ser serializada exponga los atributos que desea publicar a través de propiedades.

Para la demostración creé una clase XMLSerializable de la cual heredarán las otras que requieran de este tipo de serialización.  Esta clase abstracta provee los métodos load y save para realizar automáticamente la serialización de sus hijos.

La serialización de los objetos se realiza con el siguiente código.

        public void save(String filename)
        {
            // Creates the writing stream

            StreamWriter w = new StreamWriter(filename);

            // Creates the XML serializer with its type

            XmlSerializer s = new XmlSerializer(this.GetType());

            // Serializes the object (this self -> son)

            s.Serialize(w, this);

            // Closes the output stream

            w.Close();
        }

La carga de objetos serializados requiere de un par de verificiones adicionales.

        public bool load(String filename)
        {
            bool success = false;

            // Checks if the source file exists, otherwise there is nothing to do

            if (File.Exists(filename))
            {
                // Creates the reading stream

                StreamReader sr = new StreamReader(filename);

                // Creates the text reader from the stream

                XmlTextReader xr = new XmlTextReader(sr);

                // Creates the XML (de)serializer with its type

                XmlSerializer xs = new XmlSerializer(this.GetType());

                // Temporary storage object

                object c;

                // Checks if the received data can be deserialized

                if (xs.CanDeserialize(xr))
                {
                    // Deserialize the incoming data

                    c = xs.Deserialize(xr);

                    // Gets the type (definition) of the object

                    Type t = this.GetType();

                    // Retrieve the exposed attributes by properties

                    PropertyInfo[] properties = t.GetProperties();

                    // Walks thru all properties and loads its values on
                    // the local attributes

                    foreach (PropertyInfo p in properties)
                    {
                        p.SetValue(this, p.GetValue(c, null), null);
                    }

                    success = true;
                }

                // Closes the reader and the stream

                xr.Close();
                sr.Close();
            }

            return success;
        }

Con estas facilidades se crea una clase Empleado con atributos básicos.

    public class Empleado : XMLSerializable
    {
        ////////////////////////////////////////////////////////////////

        private int      codigo;
        private String   nombre;
        private float    salario;
        private DateTime fechaNacimiento;

        ////////////////////////////////////////////////////////////////

        // Resto del codigo
    }

Es necesario para la serialización que el objeto tenga propiedades de los atributos a exponer.

        public int Codigo
        {
            get { return codigo; }
            set { codigo = value; }
        }

        public String Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }

        public float Salario
        {
            get { return salario; }
            set { salario = value; }
        }

        public DateTime FechaNacimiento
        {
            get { return fechaNacimiento; }
            set { fechaNacimiento = value; }
        }

También es necesario que la clase cuente con un constructor sin parámetros además de los que requiera normalmente.

        public Empleado()
        {
            this.codigo          = -1;
            this.nombre          = "Sin nombre";
            this.salario         = -1;
            this.fechaNacimiento = new DateTime(1990, 01, 01);
        }

        ////////////////////////////////////////////////////////////////

        public Empleado(int codigo, String nombre, float salario, DateTime fechaNacimiento)
        {
            this.codigo          = codigo;
            this.nombre          = nombre;
            this.salario         = salario;
            this.fechaNacimiento = fechaNacimiento;
        }

Por facilidad también se sobreescribió el método ToString() para generar su representación en cadena.

        override public String ToString()
        {
            return String.Format("[Empleado: codigo = {0}, nombre = "{1}", salario = ${2}, fechaNacimiento = {3}]",
                                    this.codigo, this.nombre, this.salario, this.fechaNacimiento);
        }

Con todo esto, la aplicación de prueba crea en el primer paso a un objeto Empleado y le da valores iniciales a sus atributos, después de esto solicita su serialización.

            Empleado empleado = new Empleado(123, "Pepito Pimentón", 123456.789f, DateTime.Now);
            empleado.save("empleado.xml");

Posteriormente se crea un segundo Empleado que almacenará la información recuperada de la serialización.  Si la prueba tiene éxito, la información de los dos Empleados deberá coincidir.

            Empleado empleado2 = new Empleado();
            empleado2.load("empleado.xml");

La información presentada a través de salida estándar al ejecutar la aplicación de demostración deberá ser algo similar a lo siguiente.

Writing Object: [Empleado: codigo = 123, nombre = "Pepito Pimentón", salario = $123456,8, fechaNacimiento = 14/09/2008 11:12:10 p.m.]
Read Object:    [Empleado: codigo = 123, nombre = "Pepito Pimentón", salario = $123456,8, fechaNacimiento = 14/09/2008 11:12:10 p.m.]

El documento XML generado es bastante explícito y fácil de realizar su manipulación manual si se llegara a requerir.



  123
  Pepito Pimentón
  123456.789
  2008-09-14T22:39:52.888-05:00

Enlaces: