Truco: Cifrar el connection string

A veces, (sobre todo en hosting compartidos), tenemos que meter a pelo en el web.config el login y password del usuario con el que accedemos a la base de datos.

 

Pues bien, esto se puede cifrar de una forma muy sencilla. Por ejemplo, una sección así:

 

<connectionStrings>
  <add name="MyLocalSQLServer" 
       connectionString="Initial Catalog=aspnetdb;
       data source=localhost;Integrated Security=SSPI;" 
       providerName="System.Data.SqlClient"/>
</connectionStrings>

 

Se puede convertir a:

   1:  <connectionStrings configProtectionProvider="DataProtectionConfigurationProvider"> 
   2:    <EncryptedData>
   3:      <CipherData>
   4:  <CipherValue>AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAexuIJ/8oFE+sGTs7jBKZdgQAAAACAAAAAAADZgAAqAAAABAAAAA
   5:  Kms84dyaCPAeaSC1dIMIBAAAAAASAAACgAAAAEAAAAKaVI6aAOFdqhdc6w1Er3HMwAAAAcZ00MZOz1dI7kYRvkMIn/
   6:  BmfrvoHNUwz6H9rcxJ6Ow41E3hwHLbh79IUWiiNp0VqFAAAAF2sXCdb3fcKkgnagkHkILqteTXh</CipherValue>
   7:      </CipherData>
   8:    </EncryptedData>
   9:  </connectionStrings>

De una forma bastante sencilla siguiendo esta guia:

http://msdn.microsoft.com/en-us/library/ff647398.aspx

 

Igual es algo conocido pero que nunca está de más recordarlo.

Leer un QR desde Windows con la webcam

Si buscamos como hacernos nuestro lector de códigos QR aparecen muchas entradas de como hacerlo en dispositivos móviles, pero si lo queremos hacer desde Windows con nuestra webcam por el motivo que sea, aparte de drivers propietarios no hay mucho donde consultar.

 

Por eso, después de unas horillas de prueba/error, dejo para compartir esto:

 

http://windowqr.codeplex.com/

 

Una prueba de concepto donde uno la librería de visión por computador AForge con el decodificador  de QRs ZXING, todo ello orquestado en una aplicación WPF ( y sin memory leacks)

La clase CamControl es la que nos va a permitir gestionar las webcams de nuestro sistema.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using AForge.Video.DirectShow;
   6:   
   7:  namespace QRWindowsClient
   8:  {
   9:      /// <summary>
  10:      /// Class to control the cameras of the system.
  11:      /// </summary>
  12:      class CamControl
  13:      {
  14:          private int selectedFrameSize;
  15:          private int selectedFps;
  16:          private VideoCaptureDevice selectedDevice;
  17:          public FilterInfoCollection Devices { get; private set; }
  18:          public Dictionary<int, string> FrameSizes { get; private set; }
  19:          public List<int> Fps;
  20:   
  21:          /// <summary>
  22:          /// Gets the selected device.
  23:          /// </summary>
  24:          public VideoCaptureDevice SelectedDevice
  25:          {
  26:              get
  27:              {
  28:                  return selectedDevice;
  29:              }
  30:              private set
  31:              {
  32:                  selectedDevice = value;
  33:                  RefreshFrameSize();
  34:              }
  35:          }
  36:   
  37:          /// <summary>
  38:          /// Initializes a new instance of the <see cref="CamControl"/> class.
  39:          /// </summary>
  40:          public CamControl()
  41:          {
  42:              Devices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
  43:   
  44:              // by default select the first one
  45:              SetCamera(0);
  46:          }
  47:   
  48:          /// <summary>
  49:          /// Sets the active camera.
  50:          /// </summary>
  51:          /// <param name="index">The index of the camera</param>
  52:          public void SetCamera(int index)
  53:          {
  54:              if (Devices.Count < index)
  55:              {
  56:                  throw new IndexOutOfRangeException("There is no device with index " + index);
  57:              }
  58:   
  59:              SelectedDevice = new VideoCaptureDevice(Devices[index].MonikerString);
  60:          }
  61:   
  62:          /// <summary>
  63:          /// Sets the size of the frame.
  64:          /// </summary>
  65:          /// <param name="index">The index of the available fps</param>
  66:          public void SetFrameSize(int index)
  67:          {
  68:              if (FrameSizes.Count < index)
  69:              {
  70:                  throw new IndexOutOfRangeException("There is no framesize with index " + index);
  71:              }
  72:   
  73:              selectedFrameSize = index;
  74:              RefreshFps();
  75:              ConfigureCamera();
  76:          }
  77:   
  78:          /// <summary>
  79:          /// Sets the FPS of the active camera.
  80:          /// </summary>
  81:          /// <param name="fps">The FPS</param>
  82:          public void SetFps(int fps)
  83:          {
  84:              if (!Fps.Contains(fps))
  85:              {
  86:                  throw new IndexOutOfRangeException("There is no fps like " + fps);
  87:              }
  88:   
  89:              selectedFps = fps;
  90:              ConfigureCamera();
  91:          }
  92:   
  93:          /// <summary>
  94:          /// Refreshes the size of the frame.
  95:          /// </summary>
  96:          private void RefreshFrameSize()
  97:          {
  98:              this.FrameSizes = new Dictionary<int, string>();
  99:              int i = 0;
 100:              foreach (VideoCapabilities set in SelectedDevice.VideoCapabilities)
 101:              {
 102:                  this.FrameSizes.Add(i, String.Format("{0} x {1}", set.FrameSize.Width, set.FrameSize.Height));
 103:                  i++;
 104:              }
 105:   
 106:              selectedFrameSize = 0;
 107:              RefreshFps();
 108:          }
 109:   
 110:          /// <summary>
 111:          /// Refreshes the FPS.
 112:          /// </summary>
 113:          private void RefreshFps()
 114:          {
 115:              int MaxFramerate = selectedDevice.VideoCapabilities[selectedFrameSize].FrameRate;
 116:              Fps = new List<int>();
 117:              for (int i = 1; i < MaxFramerate; i++)
 118:              {
 119:                  if (i % 5 == 0)
 120:                  {
 121:                      Fps.Add(i);
 122:                  }
 123:              }
 124:   
 125:              selectedFps = Fps.Min();
 126:          }
 127:   
 128:          /// <summary>
 129:          /// Configures the camera.
 130:          /// </summary>
 131:          private void ConfigureCamera()
 132:          {
 133:              SelectedDevice.DesiredFrameSize = SelectedDevice.VideoCapabilities[selectedFrameSize].FrameSize;
 134:              SelectedDevice.DesiredFrameRate = selectedFps;
 135:          }
 136:      }
 137:  }

 
 

La clase QRProcess es la que se encarga de llamar al código de zxing para descifrar el QR conforme puede, ya que recibiremos más imagenes que capacidad de procesamiento tenemos. Hace uso de Monitor para hilar fino ademas de ofrecer un evento para cuando encontremos la imagen que contiene un QR válido.

 
   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Drawing;
   6:  using com.google.zxing.qrcode;
   7:  using com.google.zxing;
   8:  using com.google.zxing.common;
   9:  using System.Threading;
  10:   
  11:  namespace QRWindowsClient
  12:  {
  13:      /// <summary>
  14:      /// Class to decode qr bitmaps in a separated thread.
  15:      /// </summary>
  16:      public class QRProcess
  17:      {
  18:          public QRCodeReader reader = new QRCodeReader();
  19:          public Bitmap CurrentBitmap { get; private set; }
  20:          public event EventHandler<ProcessArgs> ResultFound;
  21:   
  22:          public bool Run { get; set; }
  23:          public bool newBitmap { get; set; }
  24:   
  25:          /// <summary>
  26:          /// When a new bitmap is available, call this method to prepare the bitmap for be consumed.
  27:          /// </summary>
  28:          /// <param name="newBitmap">The bitmap to be consumed.</param>
  29:          public void NewBitmap(Bitmap newBitmap)
  30:          {
  31:              Monitor.Enter(this);
  32:              if (this.newBitmap == false)
  33:              {
  34:                  this.CurrentBitmap = (Bitmap)newBitmap.Clone();
  35:                  this.newBitmap = true;
  36:                  Monitor.PulseAll(this);
  37:              }
  38:              Monitor.Exit(this);
  39:          }
  40:   
  41:          /// <summary>
  42:          /// Stops this instance.
  43:          /// </summary>
  44:          public void Stop()
  45:          {
  46:              Monitor.Enter(this);
  47:              this.Run = false;
  48:              Monitor.PulseAll(this);
  49:              Monitor.Exit(this);
  50:          }
  51:   
  52:          /// <summary>
  53:          /// Starts this instance.
  54:          /// </summary>
  55:          public void Start()
  56:          {
  57:              this.Run = true;
  58:   
  59:              ThreadStart start = new ThreadStart(delegate
  60:              {
  61:                  while (this.Run)
  62:                  {
  63:                      Monitor.Enter(this);
  64:                      while (this.newBitmap == false)
  65:                      {
  66:                          Monitor.Wait(this, 1000);
  67:                      }
  68:                      using (Bitmap bitmap = (Bitmap)this.CurrentBitmap.Clone())
  69:                      {
  70:                          Monitor.Exit(this);
  71:                          var result = this.Process(bitmap);
  72:                          if (!string.IsNullOrWhiteSpace(result))
  73:                          {
  74:                              if (this.ResultFound != null)
  75:                              {
  76:                                  ProcessArgs args = new ProcessArgs() { Result = result };
  77:                                  this.ResultFound(this, args);
  78:                                  this.Run = false;
  79:                              }
  80:                          }
  81:                      }
  82:   
  83:                      // the class is ready to accept a new bitmap to evaluate.
  84:                      this.newBitmap = false;
  85:                  }
  86:              });
  87:   
  88:              Thread t = new Thread(start);
  89:              t.Start();
  90:          }
  91:   
  92:   
  93:   
  94:          /// <summary>
  95:          /// Processes the specified bitmap.
  96:          /// </summary>
  97:          /// <param name="bitmap">The bitmap.</param>
  98:          /// <returns>The message decoded or string.empty</returns>
  99:          public string Process(Bitmap bitmap)
 100:          {
 101:              try
 102:              {
 103:                  com.google.zxing.LuminanceSource source = new RGBLuminanceSource(bitmap, bitmap.Width, bitmap.Height);
 104:                  var binarizer = new HybridBinarizer(source);
 105:                  var binBitmap = new BinaryBitmap(binarizer);
 106:                  return reader.decode(binBitmap).Text;
 107:              }
 108:              catch
 109:              {
 110:                  return string.Empty;
 111:              }
 112:          }
 113:      }
 114:   
 115:      /// <summary>
 116:      /// Class to pass arguments between QRProcess events.
 117:      /// </summary>
 118:      public class ProcessArgs : EventArgs
 119:      {
 120:          /// <summary>
 121:          /// Gets or sets the result.
 122:          /// </summary>
 123:          /// <value>
 124:          /// The result.
 125:          /// </value>
 126:          public string Result { get; set; }
 127:      }
 128:  }

 

Y para unir las dos clases y que se vea en pantalla lo que vamos capturando por la cam el siguiente método. Aquí lo unico que hay que destacar es el tener cuidado cuando trabajamos con punteros (variable hBitMap), que luego hay que destruirlos llamando al método DeleteObject.

 

   1:  //Reference the GDI DeleteObject method.
   2:          [System.Runtime.InteropServices.DllImport("GDI32.dll")]
   3:          public static extern bool DeleteObject(IntPtr objectHandle);
   4:   
   5:          void captureDevice_NewFrame(object sender, NewFrameEventArgs eventArgs)
   6:          {
   7:              using (UnmanagedImage uimage = UnmanagedImage.FromManagedImage(eventArgs.Frame))
   8:              {
   9:                  try
  10:                  {
  11:                      using (Bitmap image = uimage.ToManagedImage())
  12:                      {
  13:                          IntPtr hBitMap = image.GetHbitmap();
  14:                          try
  15:                          {
  16:                              BitmapSource bmaps = Imaging.CreateBitmapSourceFromHBitmap(hBitMap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
  17:                              bmaps.Freeze();
  18:                              Dispatcher.Invoke((Action)(() =>
  19:                              {
  20:                                  pictureBoxMain.Source = bmaps;
  21:                              }), DispatcherPriority.Render, null);
  22:   
  23:                              process.NewBitmap(image);
  24:                          }
  25:                          finally
  26:                          {
  27:                              DeleteObject(hBitMap);
  28:                          }
  29:                      }
  30:                  }
  31:                  catch (Exception ex)
  32:                  {
  33:                      ex.ToString();
  34:                  }
  35:              }
  36:   
  37:          }

 

Y no tiene más misterio. Espero que le sea a alguien útil.

Microsoft Crypto API (mscapi) con NCipher

Microsoft Crypto API es una interfaz para ejecutar operaciones de cifrado y gestionado de las claves que nos proporciona directamente el sistema operativo. Esta interfaz es implementada por los llamados proveedores y, por defecto, Windows trae consigo unos cuantos listos para ser usados.

 

Un proveedor, generalmente, es capaz de crear y destruir pares de claves y realizar operaciones de cifrado con ellas. Estas claves, generalmente se agrupan en contenedores, y los proveedores también son encargados de crear/destruir dichos contenedores.

image

Cada proveedor gestiona sus propios contenedores (aunque si dos proveedores son en realidad la misma implementación, usaran los mismos contenedores). Estos a su vez pueden ser a nivel de usuario o de maquina.

Para saber que proveedores tenemos instalados en nuestra máquina, basta con abrir el RegEdit y buscar en la siguiente ruta:

HKEY_LOCAL_MACHINE->SOFTWARE->Microsoft->Cryptography->Defaults->Provider

No todos los proveedores implementan las mismas funcionalidades, con lo que tambien tenemos tipos de proveedores. Para enumerar estos tipos, basta con irse a esta ruta del registro:

HKEY_LOCAL_MACHINE->SOFTWARE->Microsoft->Cryptography->Defaults->ProviderTypes

 

image

Si abrimos un proveedor podemos ver el tipo consultado la fila Type del mismo. En el ejemplo, Microsoft Base DS Cryptographic Provider es de tipo DSS Signature (tipo 3).

 

Usando un proveedor desde C#

Para realizar operaciones de cifrado desde código administrado, .NET implementa wrappers para la MSCAPI dentro del espacio de nombres System.Security.Cryptography con varias clases que engloban las diferentes llamadas que MSCAPI es capaz de ejecutar

 

image

 

Por ejemplo, el RSACryptServiceProvider tiene la capacidad de cifrar/descifrar/firmar… usando el algorithmo RSA.

Usar esta clase es tan sencillo (otro proveedores siguen el mismo patron) como instanciarla y llamar a los métodos que necesitemos.

   1:  using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
   2:          {
   3:              rsa.Encrypt(...);
   4:              rsa.Decrypt(...);
   5:              rsa.SignData(...);
   6:              rsa.VerifyData(...);
   7:              ...
   8:          }

En este código, cuando instanciamos el proveedor RSA, el wrapper va a usar el proveedor por defecto (Microsoft Enhanced RSA and  AES Cryptographic Provider) y el contenedor de usuario por defecto (ya que no estamos especificando nada). La pareja de claves, al no existir, se crearan automaticamente y persistirán dentro de nuestro SO para usarlas mas adelante.

 

Para modificar el comportamiento por defecto, podemos pasar una clase de parametros en el constructor especificando el tipo de proveedor y contenedor que queremos usar.

 

   1:  CspParameters cp = new CspParameters(1, "another provider");
   2:          cp.KeyContainerName = "UltraSecretDataContainer";
   3:          cp.Flags = CspProviderFlags.UseMachineKeyStore;
   4:   
   5:          using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
   6:          {
   7:              ...
   8:          }

 

nota: El nombre del proveedor deberá coincidir con el que existe dentro del registro que hemos visto anteriormente.

 

En el ejemplo anterior, vamos a usar otro proveedor llamado “another provider” (evidentemente no existe) y un contenedor llamado “UltraSecretDataContainer” que además se almacenará a nivel de máquina (no de usuario). El contenedor será creado automáticamente en caso de que no exista, lo mismo que su par de claves.

nota: Si intentamos descrifrar algo con un contenedor recién creado, no va a funcionar ya que un mensaje solo se puede descifrar con su par de claves, y si está recién creado, es que no se ha cifrado con ellas.

En la siguiente captura vemos el contendor y la pareja de claves con un nombre aleatorio que el proveedor ha establecido por nosotros.

image

Si lo que queremos es descartar (borrar) un par de claves, tenemos que instanciar el proveedor especificando el contenedor e indicarle que no persistan las claves.

 

   1:  // Create a new instance of RSACryptoServiceProvider that accesses
   2:          // the key container.
   3:          RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp);
   4:   
   5:          // Delete the key entry in the container.
   6:          rsa.PersistKeyInCsp = false;
   7:   
   8:          // Call Clear to release resources and delete the key from the container.
   9:          rsa.Clear();

Ojo, todos los datos que hayamos encriptado con este contenedor no va a ser posible su posterior descifrado a no ser que tengamos un backup de las claves y las importemos a un nuevo contenedor.

 

NCipher

En los ejemplos anteriores, las claves son generadas y guardadas en el equipo, incluyendo la clave privada. En estas situaciones, si nuestro equipo es comprometido, las claves pueden ser robadas o, lo que es peor, el provider puede ser cambiado para que genere un par de claves no tan aleatorias como nos gustaría, es decir, si nuestro equipo se expone, la seguridad se va al traste. Para resolver escenarios así necesitaríamos un dispositivo especial para gestionar nuestras claves, lo que es llamado un HSM (Hardware security module).

 

Este tipo de dispositivos son capaces de ejecutar operaciones criptográficas sin exponer las claves privadas bajo ningún concepto. Podremos incremental sustancialmente el nivel de seguridad en nuestras aplicaciones ya que no vamos a tener que preocuparnos sobre como gestionar las peligrosas claves privadas y, de paso, nos beneficiaremos de un hardware exclusivo para las costosas (en CPU) operaciones de cifrado.

La protección que hacen los dispositivos HSM consiste básicamente (varia según modelos) en dos puntos. Si el hardware es comprometido (abierto, manipulado etc.) las claves serán borradas automáticamente

(protección de módulo) y estas claves, además, pueden ser cifradas a su vez por un token o varios (Protección OCS, en caso de el modelo que vamos a tratar, unas tarjetas SmartCard). Si una clave es protegida por un token, el token siempre será necesario para operar con dichas claves, pero si una clave solo es protegida a nivel de modulo, solo necesitaras establecer una conexión (con las correspondientes medidas de seguridad) para operar con las claves. El ecosistema de claves, tokens, dispositivos… es llamado word y cuando configurar un mundo (protegido por los correspondientes password de administrador y contraseñas) con sus claves privadas maestras, es imposible deshacer la operación salvo mandado el dispositivo al SAT para que lo reseteen (se perderán todas las claves)

image

El dispositivo que voy a usar para las pruebas en este artículo es el Thales NCipher connect[1], un dispositivo con doble fuente de alimentación para evitar interrupciones en el servicio, con conexión ethernet y capaz de realizar unas 6000 operaciones de firma por segundo según el fabricante. Vamos, que es un aparato potente, pero para más información mejor consultar la web del fabricante en las referencias.

Network HSM nShield Connect 6000

Integración de NCipher

Cuando adquirimos un dispositivo de estos, la parte de configuración termina en el momento que esta el word configurado en el/los dispositivos y enlazado con la o las máquinas desde donde se va a lanzar las peticiones. A partir de aquí es tarea de los programadores usar las funciones que ofrece. Algunas aplicaciones comerciales ya están pensadas para este tipo de aparatos y la integración viene realizada. En Sql SErver tenemos la posiblidad de cifrar a nivel de columna o de tabla siendo casi transparente a la hora de realizar las consultas, en IIS, podemos delegar la gestión de las conexiones SSL y sus certificados, igual que en Active Directory. Pero si estamos realizando nuestra propia aplicacion, tenemos mecanismos como usar directamente la libreria PKCS11 que implementan los drivers del aparato (configurada previamente) y realizar conexiones “a pelo” u optar por usar la Microsoft Crypto API con el proveedor de nCipher.

image

Optar por PCKS11 nos proporciona la ventaja de que tiene más métodos para interactuar con nCipher pero con la Crypto API, al ser practicamente un Wrapper, poder pasar de un proveedor a otro es totalmente transparente y vamos a conseguir una programación homogénea sea cual sea el proveedor de cifrado.

 

MSCAPI y Ncipher, manos a la obra.

Nota: para seguir los ejemplos tomamos como supuesto que el ‘word’ ya está configurado en la máquina windows que va ha hostear la aplicación

Nota 2: Los comandos de consola se encuentran en: $installationPathnfastbin, normalmente c:program files (x86)nCiphernfastbin

Cuando nos enfrentamos por primera vez este dispositivo, vamos a ver que la documentación en Internet es escasa (supongo que el alto precio de estos dispositivos no los hace populares cuanto menos), así que yo recomiendo, antes que nada, comprobar que realmente el dispositivo está instalado (que el Administrador no ha metido la pata).

para ello ejecutamos:

$csptest

La máquina empezará a probar en todos los providers disponibles ciertas operaciones de cifrado. Si encontramos una de las pruebas que haga referencia a ‘nCipher Enhanced Cryptographic Provider’, es que está instalado, sino, toca la instalación del provider.

image

Para usar nCipher desde nuestro código, tan sencillo como los ejemplos anteriores, pero especificando el provider de nCipher:

   1:  CspParameters cp = new CspParameters(1, "nCipher Enhanced Cryptographic Provider");
   2:          cp.KeyContainerName = "CarlosContainer";
   3:          cp.Flags = CspProviderFlags.UseMachineKeyStore;
   4:   
   5:          using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cp))
   6:          {
   7:              ...
   8:          }

Con este código, si el contenedor no existe, ncipher lo creara en tiempo de ejecución, además del par de claves. Ejecutando el siguiente comando podemos comprobar que lo ha realizado correctamente.

$csputils.exe –U ALL –m

image

Su usamos –U es para insepccionar las cuentas de usuario, y –m las de máquina, es decir, que miramos en todos los sitios a ver que contenedores están creados en la máquina.

Las claves las podemos ver la utilidad keysafe que viene con el aparato:

image

El contenedor, si nos fijamos, no está protegido ya que no es una clave, lo que está protegido por modulo es la clave con ese nombre aleatorio que ha creado MSCAPI.

Para un checkeo de las claves

$keytst –m –p –x –s CarlosContainer (para ver las claves de ese contenedor claro)

image

 

Usar una clave protegida por token (una tarjeta) es un poco mas complicado ya que no podemos delegar en MSCAPI la creación de esta. Lo tenemos que hacer manualmente. La utilidad no permite crear una clave para MSCAPI, así que tenemos que crearla para PKCS11# y luego “meterla” o importarla en el contendor ya creado.

 

Para ello, creamos la clave con la utilidad keysafe de tipo PKCS11# y nos guardamos su ID, importante para la importación

 

image

Ahora creamos un contenedor, usando MSCAPI o “a mano”

$keytst –c MyNewContainer (to create a machine container, use –m too)

y nos guardamos su ID tambien.

Nota: Aqui hay un bug ya que si creamos el contenedor a mano, nos tendremos que mover a C:ProgramDatanCipherKey Management Datalocal para cambiar los permisos del nuevo fichero creado que representa al contenedor de la siguiente manera:

System -> full control

Administrators->full control

Users-> Read & Execute

Ahora importamos la clave en el contenedor, para ello, ejecutamos el siguiente comadno con los IDs guardados.

$cspimport –import –key NEWKEYID –appname pkcs11 NEWCONTAINERID exchange

image

No nos olvidemos importar la signature tambien

$cspimport –import –key NEWKEYID –appname pkcs11 NEWCONTAINERID signature

image

Ahora mismo,  para usar el contenedor es necesario meter el token (tarjeta) en el aparato, sino, no podremos cifrar/descifrar.

Si lo intentamos, nos aparecerá un asistente automaticamente en pantalla pidiendo el token. Para deshabilitarlo (en una aplicación web no tendria sentido una ventana en el servidor) tan sencillo como usar el flag no prompt a la hora de usar el contenedor.

cp.Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.NoPrompt;

En este ejemplo, usamos el contenedor de maquina sin el prompt. Si no está el token levantará una excepción (nada util y explicativa por cierto)

Conclusión

MSCAPI nos da un buen punto de partida para programar algo que requiera de criptografia. Además, la integración con aparatos externos es inmediata siempre y cuando estos tengan su propio provider. Lo malo de estos dispositivos, su alto precio y que la documentación es escasa, en concreto el modelo usado en las pruebas no da soporte para .NET, con lo que todo este articulo ha sido ha base de prueba error. Lo máximo que he encontrado ha sido una pregunta sin respuesta en StackOverflow, nada más.

Lineas futuras

Me gustaría completar este articulo probando otro dispositivo externo de otra marca, en concreto de Safenet[2], aunque no se si será posible. Este si ofrece soporte directo para .NET

[1]: http://www.thales-esecurity.com/Products/Hardware%20Security%20Modules/nShield%20Connect.aspx#2

[2]: http://www.safenet-inc.com/products/data-protection/data-encryption-control/datasecure/

Integración GPG, C# con The Legion of the Bouncy Castle

 

1. Introducción.

Una de las alternativas más usadas en el cifrado y firmado de ficheros mediante el esquema clave pública – privada es GnuPG[1], una aplicación open source de los algoritmos GPG de cifrado que, a día de hoy, solo dispone de una utilidad para línea de comando, poco usable si queremos integrarlo en nuestros programas .NET.

Por suerte, esta disponble Bouncy Castle, un paquete también open source de implementaciones criptográficas. Una de ellas, la que nos interesa, es PGP, y que por “suerte”, se lleva muy bien con GPG.

En este artículo veremos cómo realizar las operaciones básicas de encriptación / desencriptación tanto con GPG desde línea de comandos, como con Bouncy Castle desde nuestro código C#

2. Invitamos a los amigos GPG y Bouncy Castle

Para empezar la fiesta primero tenemos que descargarnos tanto GnuGP como Bouncy Castle de sus respectivas páginas web.

De GnuPG nos bajaremos los binarios para Windows [3] y de BouncyCastle, el código fuente junto con los ejemplos. Cuidado porque, debido a ciertas leyes estadounidenses, puedes bajar el paquete con o sin la implementación del algoritmo IDEA. Si resides fuera de Estados Unidos, te recomiendo el que lleva la implementación. [4]

Una vez descargados, procederemos a la típica instalación de GnuPG dando doble click, y descomprimiremos BouncyCastle en un directorio para después abrir la solución con Visual Studio. Seguramente tendremos que portar esta solución ya que está realizada con un Visual Studio anterior a 2008.

3. Primer invitado, GPG

Lo primero que tenemos que tener claro es que GnuGP es una aplicación bastante extensa de la cual nosotros vamos a utilizar un apenas 0,5% y lo que nos interesa a nosotros es su capacidad para generar las claves públicas/privadas, como las almacena y es capaz de exportarlas, y la encriptación-desencriptacion propiamente dicha.

Vamos a generar nuestro primer par de claves privada/publica. Nos situamos en el directorio de instalación de GnuPG y escribimos.

$gpg –-gen-key

Escogeremos la clave RSA and RSA (default) como algoritmos de encriptación y firmado, la expiración de la clave, en nuestro caso 0 y escribiremos nuestro nombre, email y comentarios para que sirve la clave. El “real name” es importante ya que es el nombre de la clave en sí para usarla con GPG, por tanto yo recomiendo un nombre sin espacios (puntos en vez de espacios) y sin caracteres raros.

image

A continuación nos pedirá una contraseña para proteger nuestra clave. Es la contraseña que se nos pedirá cada vez que queramos usar nuestra clave privada (recordemos que cuando generamos una clave, en realidad obtenemos un par de claves, privada y pública).

En mi caso no voy a poner ninguna contraseña, pero sería recomendable su uso.

Una vez terminado el proceso, nuestro sistema ya contiene nuestra nueva clave pública/privada, pero ¿Dónde? Pues GnuPG almacena todas las claves en c:Users$usernameAppDataRoaminggnupgsecring.gpg

Ese fichero en concreto, en la jerga, se llama anillo de claves o keyring. Básicamente es un fichero con todas las claves, una detrás de otra.

¿Se pueden ver en claro? Sí, pero si abrimos el fichero con el notepad el contenido es ininteligible. Debemos exportar las claves para poder verlas, guardarlas, transportalas etc..

En concreto, con el siguiente comando:

$gpg.exe --armor --output privkey.txt --export-secret-key

Conseguimos en el fichero privkey.txt nuestra clave privada (nos pedirá la contraseña si la hemos usado en la creación.

Y con este otro, la clave pública

$ gpg.exe --armor --output pubkey.txt --export nombreClave

Donde pone nombreClave, debemos escribir el nombre que le dimos nosotros. En pubkey.txt tendremos nuestra clave pública, lista para ser enviada a quien queramos para que cifre ficheros que solo puedan ser descifrados con nuestra clave privada. La clave privada deberemos guardarla, nunca publicarla.

Y ahora para cifrar/descifrar un fichero, tan sencillo como

$gpg –e –r nombreClave ficheroAEncriptar

El fichero encriptado se llamara igual que el origen, pero con la extension gpg y el nombre de la clave, nuevamente, deberá ser el que escribimos al crear la clave otra vez.

4. Segundo invitado, BouncyCastle

BouncyCastle ya trae ejemplos creados para trabajar encriptando/desencriptando ficheros, en concreto en $/crypto/tests/src/openpgp/examples/keyBaseFileProcessor.cs

En este fichero, tenemos unos métodos llamados EncryptFile y DecryptFile, donde se muestra como realizar estas tareas. Si queremos montar nuestro propio sistema está claro que este es el punto de partida para empezar a sacar ideas. Como en este mismo fichero ya tenemos un Main montando, vamos a probarlo. Para ello, en el proyecto crypto-test, en su método main, he comentado todas las llamadas a los métodos y lo he reemplazado por esto:

   1:  [STAThread]
   2:  static void Main(string[] args)
   3:  {
   4:     DateTime before = DateTime.Now;
   5:     try
   6:     { 
   7:      Org.BouncyCastle.Bcpg.OpenPgp.Examples.KeyBasedFileProcessor.Main(args);
   8:     }
   9:  
  10:   

Dejando el resto igual. Si compilamos la aplicación, veremos como BouncyCastle es capaz de usar el mismo fichero de claves de GnuPG para encriptar o desencriptar archivos, llamándolo de esta forma:

$crypto-test.exe –e ficheroAEncriptar c:Users$usernameAppDataRoaminggnupgsecring.gpg

O

$crypto-test.exe –d ficheroAEncriptar c:Users$usernameAppDataRoaminggnupgsecring.gpg “contraseña”

(La contraseña es la elegida cuando se generó la clave privada, si no se escribió ninguna, usar “” a secas)

Pero, también podría realizar la misma acción con las claves exportadas por si quisiésemos realizar la tarea de cifrado/descifrado en otra máquina donde ni siquiera tuviese instalado GnuPG

$crypto-test.exe –e ficheroAEncriptar pubkey.txt

O

$crypto-test.exe –d ficheroAEncriptar privkey.txt “”

Atención, porque lo lógico es encriptar con la clave pública y que solo el poseedor de la clave privada, nosotros, pudiera desencriptarlo.

Asi pues, con estos sencillos pasos, tenemos una solución de encriptado, desencriptado integrada en nuestras aplicaciones y con posibilidad de, en caso de necesidad, usar aplicaciones de terceros como GnuPG al usar algoritmos 100% compatibles.

[1]http://www.gnupg.org/

[2] http://www.bouncycastle.org/csharp/

[3] ftp://ftp.gnupg.org/gcrypt/binary/gnupg-w32cli-1.4.10b.exe

[4] http://www.bouncycastle.org/csharp/download/bccrypto-net-1.6.1-src-ext.zip

VS2010 tarda en lanzar una aplicacion en modo debug.

Si al lanzar una aplicacion en modo debug VS2010 se queda mucho tiempo mostrando los mensajes de output relativos al QTAgent32.exe, en concreto:

 

‘QTAgent32.exe’ (Managed (v4.0.30319)): Loaded ‘C:Program FilesMicrosoft Visual Studio 10.0Common7IDEPrivateAssembliesMicrosoft.VisualStudio.TeamSystem.Licensing.dll’

entre otras, desactivando el JIT optimization on module load se puede evitar dicho tiempo de espera.

 

¿Como hacer esto?

Debug->Options and settings

Dentro del apartado Debugging, General, Suppress JIT optimization on module load(Managed only) (desactivarlo)

En internet no hay muchas soluciones al respecto, por tanto este workaround puede valer para ir tirando.

Moles en x64 (nota mental)

Nota mental para mi mismo:

 

Si usas moles (version 0.9.4) en tu Windows x64 con tu Visual Studio 2010 (x86), instalate la version x86 de moles y no la version x64 desde el Extension Manager de Visual Studio, sobre todo si vas a trabajar con proyectos antiguos y otros equipos con Windows x86.

 

Te ahorraras muchas aspirinas, pero muchas muchas.

T 4 en la vida real

 

T4, o por su nombre, Text Template Transformation Toolkit, el framework de generación de código usado en Visual Studio y publicado hace poco por Microsoft, puede ser útil más allá de generar código fuente para nuestros proyectos en el entorno de desarrollo. En este artículo veremos cómo crear un generador de ficheros SVG a partir de una plantilla usando dicha tecnología.

…….

 

Lee el resto del artículo aquí.

http://msdn.microsoft.com/es-es/ff764873.aspx

 

 

Sql Server alias, no te olvides de…

De sobra es conocido los alias que podemos crear de Sql Server en nuestras máquinas para poder hacer que un nombre de base de datos X apunte en realidad a una base de datos Y, tal como hemos visto en muchos blogs. Pero lo que a veces se olvida es como habilitar las conexiones entrantes en la base de datos a donde apunta el Alias.

 

Recordemos que un alias, cuando nos conectamos a el, lo que realizamos normalmente es una conexión tcp/ip al puerto que hemos indicado en la configuración. 

 

 

Por tanto, la base de datos que nos conectamos tendrá que tener abierto, primero, las conexiones entrantes en el firewall de red y local de la máquina al puerto configurado, y segundo, las conexiones tcp/ip entrantes en el propio Sql Server. Para hacer esto último, tenemos que irnos al mismo panel de control donde creamos los alias, pero al apartado Sql Server Network Configuration -> Protocols -> Tcp/Ip y establecerlo a enabled.

 

 

No nos olvidemos tampoco que los Alias con un mismo nombre que algún host de nuestra red se llevan bastante mal y no es recomendable. Y si tenemos dos instancias de Sql Server 2005 y Sql Server 2008 en la misma máquina, los alias a veces nos jugarán malas pasadas así que tampoco es recomendable.

Historia programando control web para no dormir.

Los hechos ocurrieron una mañana normal y corriente de un invierno normal y corriente en una oficina normal y corriente. Las extensiones Ajax de .NET empezaban a ser instaladas en los equipos de los programadores poco a poco, conforme se iban necesitando. Visual Studio 2008 tambien se iba probrando, poco a poco, aunque eso es otra historia.

 Recien empezada la mañana, un gran terror aparecio por la pantalla de un programador. Un terror, que todavía sigue sin ser aclarado.

 

 

Lás manchas de cafe derramadas por el aterrado empleado todavía no han sido limpiadas al resurgir, de las entrañas del interprete JavaScript, el error, el terror, abominación, el chupacabras que todo programador teme en sus pesadillas más oscuras.

 

Rapidamente, el programador, debidamente adiestrado, puso en marcha el protocolo y activo el boton del panico que, como cuenta la leyenda, todo programador que ha sufrido la programación de nuestros antiguos, el ASMembler, dispone.

 

 

 A los pocos segundos, un equipo de elite de la compañia dotado de los mejores conocimientos que el tiempo, la dedicación y el dinero lo permiten, con un empujón derribarón al empleado y se pusierón a resolver EL MISTERIO.

 

 Diversos documentalistas coinciden más o menos en la versión de los hechos, que a continuación procedemos a relatar:

 

 Todo empezo con un control funcionando a la perfecto, llamado EasyPopUpControl, disponible, libre del mal y  purificado, para descargar en está página. Los posteriores interrogatorios al programador revelarón los sutiles pero relevantes cambios que se hicierón para que apareciese la abominación. Veamos una captura del control en funcionamiento.

 

 

Un funcionamiento, realmente sencillo, cuando pulsamos el boton de arriba, aparece una especie de PopUp que bloque el contenido de la pantalla. Para levantar el PopUp, basta con codificar un MiControl.Visible=true escrito en C# o VB.NET.

 

Despues de un rato, los investigadores descubrieron que la abominación solo se daba cuando nuestro control formaba parte de un UpdatePanel. Solo si estos dos factores se usaban al mismo tiempo, surgia la invocación demoniaca, y a los pocos ticks, el HORROR con mayusculas.

     <asp:UpdatePanel ID=»UpdatePanel1″ runat=»server»>
        <ContentTemplate>
            <asp:Button ID=»Button1″ runat=»server» Text=»Button» OnClick=»Button1_Click» />
            <cc1:EasyPopUp ID=»EasyPopUp1″ runat=»server»>
                <div style=»width: 100; height: 100″>
                    <asp:Button ID=»Button2″ runat=»server» Text=»Button» OnClick=»Button2_Click» /></div>
            </cc1:EasyPopUp>
        </ContentTemplate>
    </asp:UpdatePanel>

 
¿Quizás fuesen las instrucciones que conseguian una visualización correcta en tiempo de diseño las causantes?

[Category(«Appearance»)]

        [DefaultValue(«false»)]

        [Localizable(false)]

        public  bool Visible

        {

            get

            {

                if (DesignMode) return
true
;

                if (ViewState[this.ID
+ «_Visible»] != null)

                   
return (bool)ViewState[this.ID + «_Visible»];

                else

                   
return false;

            }

            set

            {

               
ViewState[this.ID + «_Visible»] = value;

            }

        }

¿O tal vez la invocación de código javascript embebido en la Dll? No parece que ninguna de esas fuesen las causas del daño. Fue algo mucho más ingenio, motivado quizás por la cada vez más cerca fecha de entrega,  seguridad del programador o confianza en las observaciones y sugerencias de Visual Studio. El caso es que al cambiar las caracteristicas de la propiedad del control ‘Visible’ de «public  bool Visible» a «public override bool Visible», tal como sugeria Visual Studio, ocurria la desgracia. Está propiedad overrideada, sobrevivio al paso de los años oculta por el buen funcionamiento del control sin que nadie se percatase de ella. Es solo cuando las extensiones ASP.NET hicieron acto de presencia cuando ocurrio lo ya contado.

 ¿Porquè?, muchas discusiones ha habido en la cafeteria sobre este tema, muchos correos enviados. Incluso se sugirio enviar una misiva a la santa sede en seattle, aunque finalmente no se hizo. La solución, evidentemente fue reemplazar la palabra prohibida override, por una que llenaba de esperanza a todos los programadores, «new».

 

Y ustedes, tengan cuidado en sus casas, nunca se sabe que errores, abominables, se ocultan entre nuestros programas.

 

P.D: En realidad este post era para describir el como se hizo este control, pero bueno. El control está para descargar por si alguien lo quiere.

EasyPopUpControl

 

Web Controls como si estuviese en segundo.

 


A veces, en nuestros desarrollos ASP.NET nos encontramos con partes comunes en un proyecto que, como todos sabemos, encapsulamos en UserControls rapidamente y sin complicaciones. Pero cuando queremos traspasar las fronteras del proyecto y/o hacer algo más grande, reutilizable o novedoso, la cosa se complica ya que nos habremos metido en el apasionante y complicado mundo de los controles  Web. En este primer post intentaré plasmar lo que he aprendido en este campo ya que lo que he encontrado por ahí no me llega a convencer.



Como punto de partida decir que si queremos ser con «Web Control Developer» necesitamos, evidentemente, conocimientos del viejo HTML, manejo en el arcano mundo JavaScript y como no, conocimientos de la plataforma .NET.


 



  1. Punto de partida

La ayuda de las msdn ofrece una guía más o menos completa sobre este tema, llamada Developing Custom ASP.NET Server Controls aunque un poco desorganizada desde mi punto de vista. Tambien ayuda la plantilla de Visual Studio llamada ASP.NET Server Control, aunque menos.



 


Como el movimiento se demuestra andando, vamos a ver como realizar un control más o menos complejo, alejado del típico hola mundo que muestran todas las guias. 


2. Definición del control 


En vez de definir un control de cero, vamos a intentar  portar algo ya hecho, algo vistoso, bonito y que no necesite mucha explicación y es aquí cuando entra en escena DHTML Suite. Esta Suite ofrece una colección de controles importantes, todos ellos realizados con JavaScript, que bajo la excusa de AJAX ofrece un autentico FrameWork para realizar cosas vistosas. Una demo de lo que es capaz de hacer la tenéis aquí.


 De toda la lista de controles el que vamos a «encapsular» en forma de control ASP.NET es el InfoPane, un control que muestra una columna a la izquierda del navegador donde mostrar categorías de enlaces o información y la posibilidad de plegar y desplegar dichas categorías.




 


Si queréis examinarlo deberéis de bajar la suite desde este enlace y descomprimirlo. El ejemplo se encuentra en la ruta dhtml-siutedemosdemo-info-pane1.html. Si abrís esa página el navegador os avisara que ha bloqueado el contenido, por medidas de seguridad. Es necesario permitirle que abra completamente la página para ver el panel (el código javascript lo podéis auditar y no es malicioso, para los más escépticos).


 Vamos a ver que código tiene la página de ejemplo abriéndola con nuestro editor preferido, lo importante es esto:


 


<script type=»text/javascript» src=»../js/ajax.js»></script>
<script type=»text/javascript» src=»../js/dhtml-suite-for-applications.js»></script>

</head>

<body>            
    
<div class=»DHTMLSuite_xpPane»>
        
<div id=»pane1″>
                  
<div>ContenidoPane1</div>    
            
</div>
            
<div id=»pane2″>
                  
<div>ContenidoPane2</div>          
            
</div>
            
<div id=»pane3″>
                  
<div>ContenidoPane3</div>          
            
</div>
      
</div>
      
<div id=»otherContent»>
            …….
      
</div>
<script type=»text/javascript»>
var infoPane = new DHTMLSuite.infoPanel();
DHTMLSuite.commonObj.setCssCacheStatus(true);
infoPane.addPane(‘pane1’,‘Panel 1’,500,‘cookie_pane1’);
infoPane.addPane(‘pane2’,‘Panel 2’,500,‘cookie_pane2’);
infoPane.addPane(‘pane3’,‘File and folder tasks’,500,‘cookie_pane3’);
InfoPane.init();
</
script>
….. 

 


 En primer lugar tenemos la carga de los js con el código de DHTML. En otra parte de la página tenemos un DIV con class=»DHTMLSuite_xpPane» y dentro una serie de DIVS con id concreto. Por ultimo otro javascript con la inicialización del control.



En dicha inicialización es importante recalcar que la nueva instancia detectará el DIV con class=»DHTMLSuite_xpPane» sin necesidad de pasarle un ID en el constructor. También hay que fijarse en que por cada DIV «hijo» hay que hacer un addPane para añadirlo a la colección de paneles hijos que se mostrará en el control, que a partir de ahora llamaremos InfoPane. ¿Como encapsulamos todo esto dentro de un control (web) ASP.NET?


 Mi experiencia personal me dice que en este tipo de controles es muy necesario tener claro de antemano como vamos a querer el control y que funcionalidad le vamos a dar, ya que puede ser un poco tedioso el desarrollo y, sobretodo, el cambio. No basta con tener claro como se realizan controles, aunque ayuda bastante. De ahí la motivación a la hora de escribir este texto. Pensemos en el diseño del control, antes incluso de saber como se hacen.


 Diseño de InfoPane


Siguiendo la filosofía ASP.NET es evidente que la idea es tener un control que se use o que se escriba con un código ASPX más o menos así:


<cc1:InfoPaneContainer ID=»InfoPaneContainer1″ runat=»server» CssUrl=»/Css/info-pane.css»>
   
<cc1:InfoPaneElement runat=»server» ID=»infoPaneElement1″>
   …contenido dentro del subpanel 1….
   
</cc1:InfoPaneElement>
   
<cc1:InfoPaneElement runat=»server» ID=»infoPaneElement2″>
   …contenido dentro del subpanel 2….
   
</cc1:InfoPaneElement>
</cc1:InfoPaneContainer>



 Sin necesidad de escribir tedioso JAVASCRIPT o HTML adicional. También nos podría ocurrir que en cualquier parte del código trasero necesitásemos algo así:


InfoPaneElement e=new InfoPaneElement();


InfoPaneContainer1.AddPanel(e);


Es decir, necesitamos que admita instancias DECLARATIVAS en ASPX o imperativas dentro del código C#. Para empezar ya sirve la idea y nos podemos poner manos a la obra aunque evidentemente siempre podremos añadir más funcionalidad como decirle el tamaño de los InfoPaneElements, visibilidad de los mismos, o lo que se nos ocurra.


Resumiendo, tenemos que programar, no uno, sino dos controles, uno llamado InfoPaneElement y otro llamado InfoPaneContainer.


 


Programando el control


No voy a explicar como funcionan en profundidad los controles ni su arquitectura ya que existe bastante literatura al respecto. Lo único que hay que tener claro es como funciona el ciclo Render en cada post-Back y el VIEWSTATE. Básicamente, en el evento Render de un control podemos escribir código HTML que se enviara directamente al output y se ejecuta cada vez que hay un PostBack. El ViewState es bastante conocido y no necesita explicación. Al lio, ejecutemos el Visual Studio (en mi caso VS2008 beta 2).


 Lo primero que necesitamos es una solución con dos proyectos, uno de librería de controles y otro ASP.NET de los de toda la vida para probar el control mientras lo desarrollamos. La aplicación de prueba necesitará una referencia al proyecto libreriaControles, ahora sería un buen momento para hacerlo.




 En LibreríaControles añadiremos dos ficheros cs, uno para cada tipo de control que vamos a programar, yo los llamaré, en una muestra de originalidad, InfoPaneElement e InfoPaneContainer. Para simplificarnos la tarea, podremos hacer un copia-pega del código que viene en el fichero-control de ejemplo o usar la plantilla correspondiente. Ahora toca programár


Codificación de InfoPaneElement


Empezaremos por el contenedor. Abrimos el fichero InfoPaneContainer.cs y, después de heredar de WebControl tendremos que modificar el método RenderContents para escribir en el documento html las etiquetas de inicio y cierre del control, además del código Javascript que lo inicializa, aunque si consultamos un poco los métodos disponibles, mejor realizar estas tareas en los métodos RenderBeginTag y RenderEndTag, dejando el RenderContents para el contenido en sí del control, algo así:


 


protected override void RenderContents(HtmlTextWriter output)
{
    RenderElements(output)
;    
}

public override void RenderBeginTag(HtmlTextWriter writer)
{
    writer.Write(
«<div class=»DHTMLSuite_xpPane»>»);    
}

public override void RenderEndTag(HtmlTextWriter writer)
{
    writer.Write(
«</div>»);
    
writer.Write(RenderInitScript());
}

El método que se hace referencia, renderElements, se encarga de renderizar los «subcontroles» contenidos en el InfoPaneContainer. A su vez, RenderInitScript se encarga de renderizar el código Javascript necesario para que funcione el control, tal como vimos anteriormente. La pregunta del millón es ¿Cómo sabes que subcontroles tiene el control? Y lo más importante, ¿Cómo hacer que el control PUEDA tener subcontroles anidados?


 


Para responder a las preguntas es necesario saber que ASP.NET, para interpretar el código aspx, dispone de un parseador como cualquier otro compilador o interprete. Por tanto, si nuestro control va a contener subcontroles propios, hay que indicarle que parsee nuestros controles hijos o, en nuestro caso, que parsee los InfoPaneElement ¿Cómo?, con un atributo (como no) llamado ParseChildren. Se usa así:


namespace LibreriaControles
{
    [ToolboxData(
«<{0}:InfoPaneContainer runat=server></{0}:InfoPaneContainer>»)]
    [ParseChildren(
true«InfoPaneElements»)]
    
//[ParseChildren(false)]  
    
public class InfoPaneContainer : WebControl
    {
        
private ArrayList _infoPaneElements;
        
[Category(«Appearance»)]
        [DefaultValue(
«»)]
        [Localizable(
false)]
        
public ArrayList InfoPaneElements
        {
            
get
            
{
                
if (_infoPaneElements == null) _infoPaneElements = new ArrayList();
                return 
_infoPaneElements
            
}
        }

Al atributo ParseChildren le pasamos el atributo true para activarlo y luego una propiedad ICollection que es donde dejará el motor ASP.NET las instancias de nuestros controles hijos. Yo he escogido ArrayList como el tipo de la lista, pero lo conveniente sería tener una lista tipada para evitar problemas.


Por tanto en la lista InfoPaneElements tendremos todas las instancias de los controles hijos. Veamos como usarla. Antes nos hemos quedado en el método RenderElements, que es un método propio, veamos la implementación:


        private void RenderElements(HtmlTextWriter output)
        {
            
foreach (InfoPaneElement e in this.InfoPaneElements)
            {
                e.RenderControl(output)
;
            
}
        }


Recorremos la lista de métodos hijos e invocamos su render, para que ellos mismos se auto dibujen. En principio, la inercia y los ejemplos nos llevan a realizar el render del objeto hijo en este método, pero es conveniente que cada control se renderize a si mismo.


Tambien nos quedaba pendiente el método RenderInitScript() el cual nos generará el javascript de incialización:


        private string RenderInitScript()
        {
            StringBuilder output 
= new StringBuilder();
            
output.Append(«<script type=»text/javascript»>»);
            
output.Append(«var infoPane = new DHTMLSuite.infoPanel();»);
            
//output.Append(«DHTMLSuite.commonObj.setCssCacheStatus(true);»);
            
foreach (InfoPaneElement e in this.InfoPaneElements)
            {
                output.AppendFormat(
«infoPane.addPane(‘{0}’,'{1}’,500,’cookie_{0}’);»,e.ID, e.Text);
            
}
            output.Append(
«infoPane.init();»);
            
output.Append(«</script>»);
            return 
output.ToString();
        
}

Como vemos, la filosofia es la misma, recorremos la colección de controles, y para cada control, hacemos lo que haya que hacer. En este caso esto no se ha delegado al control ya que esta inicalización necesitamos hacerla de todos a la vez para mantenerlo todo junto y organizado en el código javascript resultante.


Otra cosa que le podemos agregar al control son propiedades que se inicialicen declarativa o imperativamente.


<cc1:InfoPaneContainer ID=»InfoPaneContainer1″ runat=»server» CssUrl=»~/Css/info-pane.css»>

En este caso vamos a agregarle esta propiedad llamada CssUrl. Dos partes, la propiedad con sus atributos para hacerla «bonita» en el visual Studio y el método que la va a renderizar.


[Category(«Appearance»)]
[DefaultValue(
«»)]
[Localizable(
false)]        
public Uri CssUrl
{
    
get
      
{
          
if (ViewState[«CssUrl»] != null)
              
return (Uri)ViewState[«CssUrl»];
            else
              return null;
      
}
      
set
      
{
          ViewState[
«CssUrl»= value;
      
}
}
private string RenderCssLink()
{
    
if (CssUrl == null)
          
throw new InvalidOperationException(«Falta la url de las css»);
      else
            return 
String.Format(«<link rel=»stylesheet» href=»{0}» media=»screen» type=»text/css»/>{1}» , CssUrl, Environment.NewLine);
}

Lo más importante para destacar aquí es que si no está establecida la Cssurl levantaremos una excepción, no dejaremos que este error traspase la frontera y se convierta en un error javascript. Quizás InvalidOperationException no sea el tipo adecuado, pero para nuestro control, sobra. Tambien como usaremos el ViewState para guardar el estado de dicha propiedad. Si no lo hiciesemos, los cambios que hiciesemos por código no se verian reflejados en cada PostBack.


 


¿Cuándo se debe renderizar esta propiedad? En el RenderContet no porque debe ir en la cabecera, en el beginTag tampoco. Su sitio es el OnPreRender


 protected override void OnPreRender(EventArgs e)
{
    
this.Page.Header.Controls.Add(new LiteralControl(RenderScript()));
      this
.Page.Header.Controls.Add(new LiteralControl(RenderCssLink()));
}

RenderScript es el método que hará los «includes» del código fuente de Dhtml Suite


        private string RenderScript()
        {
            
//string scriptUrlInfoPane = Page.ClientScript.GetWebResourceUrl(typeof(LibreriaControles.InfoPaneContainer), «LibreriaControles.dhtmlSuite-infoPanel.js»);
            //string scriptUrlCommon = Page.ClientScript.GetWebResourceUrl(typeof(LibreriaControles.InfoPaneContainer), «LibreriaControles.dhtmlSuite-infoCommon.js»);
            //string scriptUrlDynamicContent = Page.ClientScript.GetWebResourceUrl(typeof(LibreriaControles.InfoPaneContainer), «LibreriaControles.dhtmlSuite-dynamicContent.js»);

            
string scriptUrlInfoPane «/Js/dhtmlSuite-infoPanel.js»;
            string 
scriptUrlCommon «/Js/dhtmlSuite-common.js»;
            string 
scriptUrlDynamicContent «/Js/dhtmlSuite-dynamicContent.js»;


            
StringBuilder sb = new StringBuilder();
            
sb.AppendFormat(«<script type=»text/javascript» src=»{0}»></script>{1}», scriptUrlCommon, Environment.NewLine);
            
sb.AppendFormat(«<script type=»text/javascript» src=»{0}»></script>{1}», scriptUrlDynamicContent, Environment.NewLine);
            
sb.AppendFormat(«<script type=»text/javascript» src=»{0}»></script>{1}», scriptUrlInfoPane, Environment.NewLine);           
            
            return 
sb.ToString();
        
}

Os dejo comentado, para los curiosos, la forma que habría que  usar para extraer el código Javascript del ensamblado. Y esta comentado precisamente porque con estas librerias de DhtmlSuite, no funciona pero esto ya es otro tema.


 


Ahora toca codificar el InfoPaneElement.


 


InfoPaneElement


 


Este es sencillo:


namespace LibreriaControles
{
    [ToolboxData(
«<{0}:InfoPaneElement runat=server></{0}:InfoPaneElement>»)]
    [Serializable]
    [ParseChildren(
false)]
    
public class InfoPaneElement :WebControl
    {
        
string _text «»;
        public string 
Text
        {
            
get
            
{
                
return _text;
            
}   
            
set
            
{
                _text 
= value;
            
}
        }
        
protected override void RenderContents(HtmlTextWriter writer)
        {
            
foreach (Control wb in this.Controls)
            {
                wb.RenderControl(writer)
;
            
}
            
        }
 
        
public override void RenderBeginTag(HtmlTextWriter writer)
        {
            writer.Write(
«<div id=»{0}»><div>»this.ID);
        
}

        
public override void RenderEndTag(HtmlTextWriter writer)
        {
            writer.Write(
«</div></div>»);
        
}
    }
}

Destacar el ParseChild(false), necesario si queremos que nuestro control pueda contener otros controles como labels, calendars y demas. Recordemos que parseChild solo se inicializa con true si queremos parsear nuestros propios controles. La propiedad Text no es el texto del control, es el título del control y se usará en el método RenderInitScript de InfoPaneContainer.


 


Prueba de los controles


 


Para probar los controles en el proyecto Web, página Default.aspx los usamos tal que así:


 


<%@ Page Language=»C#» AutoEventWireup=»true» CodeBehind=»WebForm1.aspx.cs» Inherits=»TestControls.WebForm1″ %>

<%@ Register Assembly=»DhtmlSuiteControls» Namespace=»LibreriaControles» TagPrefix=»cc1″ %>

<!DOCTYPE html PUBLIC «-//W3C//DTD XHTML 1.0 Transitional//EN» «http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd»>

<html xmlns=»http://www.w3.org/1999/xhtml» >
<head runat=»server»>
    
<title>Untitled Page</title>
</head>
<body style=»margin:0px»>
    
<form id=»form1″ runat=»server»>
    
<div>
        
<cc1:InfoPaneContainer ID=»InfoPaneContainer1″ runat=»server» CssUrl=»~/Css/info-pane.css»>
            
<cc1:InfoPaneElement ID=»pane1″ runat=»server» Text=»Aaaaaa»>
                
<div>
                    
<a href=»fgdfg»>dfgdfgdfgdfg</a>
                    salida sin control
                    
<asp:Label ID=»milabel» runat=»server»> contenido label</asp:Label>
                    
<asp:Button ID=»miboton» runat=»server» />
                </
div>
            
</cc1:InfoPaneElement>
            
<cc1:InfoPaneElement ID=»InfoPanecontainer2″ runat=»server» Text=»Titulo B»>
                Texto del InfoPaneelement
            
</cc1:InfoPaneElement>
        
</cc1:InfoPaneContainer>
    
</div>
    
</form>
</body>

Y la salida correspondiente:



 


 


Cosas para mejorar y conclusión


 


Una de las cosas que más complicado se puede llegar a hacer es el control de los script. En este ejemplo esta MUY mal gestionado y lo ideal sería usar ScriptManagers para controlar su cacheo y administración en general (recordar que para que funcione es necesario copiar los archivos que incluimos en el proceso RenderScript). Eso lo dejaremos para otra entrada. También existe un tema bastante interesante y es que podemos crear un gestor de propiedades invocable desde el panel de propiedades de visual Studio ( estilo Items de un ListBox). En este ejemplo vendría bien para gestionar los InfoPaneElements que contiene un InfoPaneContainer.


En conclusión, hemos visto que hacer un control re-usable en varios proyectos no es muy difícil (sobre todo si nos dan el javascript como en este ejemplo ;)) y sin embargo, la encapsulación y control que te da sobre la aplicación Web es muchísimo más potente que los controles de usuario Web. Si lo quereis probar, adjunto un zip con todo el proyecto para Visual Studio 2008 RTM


P.D: Agradecimientos a Jesus Jimenez Antelo de Ilitia. Realmente todo el trabajo es suyo 😉


P.D. v2: Aprovecho mi primera entrada para presentarme a Geeks.ms y dar las gracias Rodrigo Corral por proporcionarme este blog. Espero que os haya resultado interesante. Los comentarios, correciones y criticas son bienvenidos.