sábado, junio 27, 2009

Sistema de plugins en C# usando Reflexión

En este post os voy a describir un pequeño sistema de plugins que implementé para un proyecto y que me resultó de mucha utilidad y sobre todo, me permitió aumentar el sistema sin tener que cambiar nada de lo ya establecido (códigos, recompilaciones, etc.).

La idea es disponer de un sistema de plugins para un CMS (gestor de contenidos) de forma que cada plugin sea autogestionado y no haya que tocar nada del sistema a la hora de introducir un nuevo plugin, salvo lógicamente introducir su definición en base de datos. En el proyecto que implementé, los plugins eran elementos visuales que permitían realizar la maquetación de las páginas mostrando el contenido de base de datos de diferentes formas (contenido tabulado, histórico, contenido con scroll, publicidad, etc.).



No voy a entrar en cómo implementar toda la estructura del sistema de elementos visuales (será para un artículo posterior), lo importante es cómo podemos, acceder a clases de c# sin utilizar elementos tipados. Es decir, lo normal para instanciar un objeto es utilizar la siguiente nomenclatura:

TipoObjeto miObjeto = new TipoObjeto(parametros);

En el caso de un sistema dinámico de plugins, no podemos utilizar esa nomenclatura porque no sabemos a priori la clase del objeto a utilizar. En esta situación, la reflexión se nos ofrece como técnica perfecta.

Implementación del sistema de plugins

Los plugins se dan de alta en base de datos, cada proyecto puede utilizar su propia definición de tablas. En mi caso, creé dos tablas, una para guardar la definición del elemento y otra para almacenar las propiedades modificables del elemento. Por ejemplo, uno de los plugins era para introducir un bloque de código HTML directamente en la página. La definición de este elemento es:


y la definición de sus propiedades es:


Lo importante de los datos almacenados para cada elemento es el campo "clase" en el que se guarda la clase de código que implementa la funcionalidad de este elemento. En este caso, la clase es plugins.elementoHtml. Este campo se utiliza luego para la reflexión.

Interfaz de los plugins
Este paso no es necesario pero es bueno que todas las clases que se encarguen de plugin implementen una interfaz obligando a que el método público de acceso sea siempre igual y no haya fallos de parámetros. En mi caso, la interfaz definía los siguientes métodos:

La interfaz define dos métodos, uno para obtener el código html del plugin cuando se debe insertar en el centro de la página y otro para cuando se debe insertar en el lateral de la página. Los parámetros que se pasan son el id particular para cada elemento y un object extras con parámetros extras que hicieran falta en cada caso.

El quid de la cuestión

En cada proyecto, obtendremos referencias a nuestros plugins de la manera que queramos. El quid de la cuestión se encuentra en las líneas que utilizan la reflexión para acceder a los métodos de la clase de cada plugin:




Lo que hacemos es:
  1. Obtener el campo "clase" de la base de datos y llamar al método Type.GetType para obtener el tipo del elemento.
  2. A través de reflexión podemos obtener acceso al constructor e invocarlo pasándole los argumentos necesarios, en este caso null porque no hay argumentos.
  3. Obtener la información del método "Renderizar" que es el que utilizamos de cada clase y que define la interfaz.
  4. Invocamos el método pasándole el objeto inicializado mediante el constructor y le pasamos un vector de objects con los parámetros que el método está esperando.
  5. El resultado del método (en este caso un string), se obtiene haciendo un cast a string.
Conclusión
La reflexión es una herramienta muy potente dentro del lenguaje .net que permite llamar a métodos y objetos sin conocer previamente el tipo. Esto permite generar sistemas como el descrito de una forma eficiente. El problema estriba en que, inicialmente no es un tipo de programación sencilla y requiere un tiempo para acostumbrarse a cómo funciona.

Espero que os haya gustado este artículo y que os sirva esta idea para vuestros desarrollos. En mi caso, me ha sido de mucha utilidad.




domingo, junio 14, 2009

Problema de expiración de la sesión en ASP.net

Los códigos de este post los podéis descargar desde aquí

Finalmente, por fin he encontrado un rato para escribir un post en el blog. Tenía en mente varios temas pero al fin me he decidido por éste que me parece bastante interesante y útil para aquellos que desarollamos aplicaciones en asp.net. Pongámonos en situación, cuando escribimos aplicaciones, una de las herramientas más utilizadas para mantener la persistencia de los datos es la sesión, almacenamos en sesión tablas con las que trabaja el usuario por pantalla, ids de filas, usuarios, variables, etc. todo ello para que cuando el usuario pulse algún botón y se realice un postback, podamos realizar las acciones correspondientes. Es decir, la sesión nos mantiene conectado con el usuario en un entorno desconectado. El problema viene cuando el tiempo de sesión expira, si el usuario no ha realizado ninguna acción pasados x minutos (5 por defecto), el servidor (IIS) elimina su sesión. En el momento en que el usuario pulse un botón y se vaya al servidor, nos encontraremos con que aquellos datos que habíamos guardado en la sesión ya no existen y (dependiendo de cómo controlemos esta situación) saltará una excepción en algún punto de nuestro código, echándonos fuera de la aplicación, o saltando una fantástica pantalla amarilla o algo parecido.

En aplicaciones en las que las operaciones se realicen en el servidor no vamos a tener problemas con la sesión porque cada vez que el usuario realice un postback, la cuenta de expiración de la sesión se reinicirá. En el caso que hagamos aplicaciones que hagan uso de la lógica del cliente, tipo blogs, editores wysiwyg, o que el usuario realice operaciones largas en el explorador sin tener que ir al servidor, el tiempo de expiración de la sesión se convierte en un gran problema para el programador y en una gran molestia para el cliente. Imaginaos escribiendo un post en un blog y que cuando lleváis 30 minutos escribiendo, habéis acabado el post le dais a guardar y salta una excepción, perdiendo todo el trabajo...

Posibles soluciones a este problema
Existen varias soluciones a este problema, que dependiendo de dónde esté alojada nuestra aplicación nos serán factibles o no.

Solución 1: Web.config

La solución más fácil es incorporar en nuestro web.config la etiqueta "sessionState" con el atributo "timeout" indicándole el número de minutos que deseamos que se mantenga la sesión (dentro de la sección system.web):



En este caso, hemos aumentado el tiempo de sesión a 60 minutos, antes de que el servidor la elimine. Sin embargo, me he encontrado alojamientos en los que, aunque en mi web.config tenga incluido este tag, el tiempo de sesión viene marcado por el servidor sin posibilidad de cambiarlo desde aquí.

Solución 2: Pool de aplicaciones
La segunda solución con la que contamos es la de crear un pool de aplicaciones en el IIS para nuestra aplicación. En dicho pool podemos especificarle el tiempo de sesión que queremos. Además, al crear un pool propio, se lanza un proceso en el sistema para nosotros solos. Esto es bueno en el caso de alojamientos compartidos en los que en el mismo proceso puede haber muchas aplicaciones ejecutándose. Siempre es aconsejable tener nuestro propio pool de aplicaciones, el cual se crea de la siguiente manera:

1. Desde el administrador de IIS, pulsamos con el botón derecho sobre "Grupos de aplicaciones" y seleccionamos un nuevo grupo:


2. Le damos un nombre al nuevo grupo de aplicaciones:


3. Lo siguiente es configurar las opciones de rendimiento para este grupo de aplicaciones, como la cantidad de memoria a asignar, el tiempo de reciclado del proceso o el tiempo de sesión para este grupo, entre otras opciones:


4. Por último, a un sitio o directorio web, le asignaremos este grupo de aplicaciones desde sus propiedades:


Solución 3: Servicio web de Echo
Para implementar las dos soluciones anteriores necesitamos tener control sobre el servidor (cosa que no siempre es así). En el caso que tengamos alojamientos compartidos, podemos utilizar esta opción (es la que utilizo ahora). Normalmente, un portal web dispone de dos partes, la pública (en la que normalmente se puede funcionar con cookies) y la privada en la que trabajaremos con sesiones. Es en la parte privada donde utilizaremos esta opción. La idea es implementar un servicio web que simplemente realice un eco, reciba un parámetro y lo devuelva. Habilitamos la sesión para el servicio web con lo que cada vez que se llame a este servicio, la cuenta de expiración de la sesión se reinicirá. Desde las página de gestión se llama, vía AJAX, periódicamente (yo lo tengo cada 50 segundos) a dicho servicio web para que no reinicie la cuenta. De forma esquemática, la solución es la siguiente:


El código del servicio web es el siguiente:

Tenemos que introducir el using System.Web.Script.Services para poder utilizar este servicio web vía AJAX. Además, la definición del servicio web incluye la sentencia [WebMethod(true)] indicando que se utilizará la sesión en ese método del servicio web. Para llamar a dicho método desde la página maestra (lo normal) o desde cualquier página de nuestra aplicación utilizaremos el siguiente código:

a. Incluir una referencia web al servicio web creado.
b. Dar de alta el servicio web mediante el ScriptManager de la página
En la propiedad Path pondremos la ubicación del fichero .asmx (servicio web). Si, al crear el servicio web, separados el código del fichero, se creará un fichero .asmx con la definición del servicio web donde le hayamos dicho y un fichero .cs con los códigos dentro de la carpeta App_Code.


c. Crear la función Javascript que llama al servicio web


d. Crear el temporizador para que se repita la llamada cada x segundos

Ponemos la función en el onload de la página para que se ejecute al iniciar la página. En este caso esta puesto que se repita la llamada cada 50 segundos (50.000 milisegundos)


Conclusión
Esta última es una solución sencilla que nos puede servir en cualquier entorno en el que trabajemos con aplicaciones asp.net. Utilizando el servicio de echo, nos aseguramos que la sesión del usuario no va a expirar independientemente del tiempo que esté sin hacer ningún postback. En el caso que el usuario cierre la ventana del explorador, se deja de llamar al servicio con lo que la sesión expirará en los 5 minutos correspondientes. Por supuesto, siempre podemos colocar el botón de cerrar que matará la sesión. Como siempre, espero que os sea de utilidad este post y gracias por leer este blog (ya sois un montón).