Editando las connection strings «en caliente» de un proyecto ASP.net

Un problema peculiar con el que me he encontrado recientemente tiene que ver con dos equipos con la misma base de código y un punto de sincronización común, que en este caso es o bien dos bases de datos distingas del mismo servidor, o a la misma base de datos con diferentes credenciales, en un gráfico como el que se muestra.

Capture

Para guardar la configuración de la base de datos recurrimos habitualmente al apartado connectionStrings de nuestro fichero web.config. Dicho fichero cuenta además con diferentes transformaciones según la configuración de build seleccionada, como muestra la siguiente imagen:

Capture_debug

Pero… las transformaciones solamente se ejecutan al desplegar un proyecto…

El hecho de no poder ejecutar las transformaciones en tiempo de compilación no nos permite depurar nuestra solución con las cadenas de conexión necesarias, y, aunque existe una manera de transformar el fichero web.config durante la compilación, quería buscar una aproximación diferente.

Editando el objeto ConnectionStrings

Dentro del código de nuestra aplicación disponemos del objeto ConfigurationManager.ConnectionStrings que nos da una colección de solo lectura en la que podemos acceder a los datos. Si intentamos escribir en dicha colección obtenemos la siguiente excepción:

An exception of type 'System.Configuration.ConfigurationErrorsException' occurred in System.Configuration.dll but was not handled in user code

Additional information: The configuration is read only.

Sin embargo si vamos al código fuente de ConnectionStrings veremos que el atributo connectionString tiene setter, con lo cual en teoría podemos fijar el valor:

public string ConnectionString {
    get {
        return (string)base[_propConnectionString];
    }
    set {
        base[_propConnectionString] = value;
    }
}

Si vamos un poco más abajo nos encontraremos con la clase ConfigurationElement, cuyo método IsReadOnly() se comprueba al intentar fijar un valor.

protected void SetPropertyValue(ConfigurationProperty prop, object value, bool ignoreLocks) {
if (IsReadOnly()) {
    throw new ConfigurationErrorsException(SR.GetString(SR.Config_base_read_only));
}

public virtual bool IsReadOnly() {
    return _bReadOnly;
}

Este valor IsReadOnly lee una variable _bReadOnly, y no sería maravilloso que pudiéramos editar esa variable y editar nuestras cadenas de conexión? Pues gracias a Reflection podemos hacerlo!:

ConnectionStringSettings connectionString = ConfigurationManager.ConnectionStrings[0];
var field = typeof(ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(connectionString, false);
connectionString.ConnectionString = "CustomConnectionString";
field.SetValue(connectionString, true);

Tras nuestra edición podemos volver a establecer el atributo _bReadOnly a true restaurando el bloqueo de la escritura. Hemos de tener en cuenta que este proceso ha de realizarse lo antes posible en nuestra aplicación, así que yo usaría Startup.cs o Global.asax.cs (recordemos que este último desaparece con ASP.net vNext).

Cargando nuestras connection strings personalizadas

Una vez que sabemos que podemos cargar las connection strings en caliente, el siguiente paso es cargar el fichero que contiene las transformaciones:

string path = HostingEnvironment.MapPath("~/Web.TEAM1.config");
XmlDocument doc = new XmlDocument();
doc.Load(path);
XmlNodeList list = doc.DocumentElement.SelectNodes(string.Format("connectionStrings/add[@name='{0}']", "Title"));
XmlNode node = list[0];
string connectionStringToReplace = node.Attributes["connectionString"].Value;

En este caso cargamos el fichero de TEAM1, buscamos la cadena de conexión con la clave especificada y la asignamos a la variable connectionStringToReplace.

Código completo

private void ReplaceConnectionStrings(string team)
{
    string path = HostingEnvironment.MapPath(string.Format("~/Web.{0}.config", team));
    XmlDocument doc = new XmlDocument();
    doc.Load(path);

    foreach (ConnectionStringSettings connectionString in ConfigurationManager.ConnectionStrings)
    {
        XmlNodeList list = doc.DocumentElement.SelectNodes(string.Format("connectionStrings/add[@name='{0}']", connectionString.Name));
        if (list.Count == 0) continue;

        XmlNode node = list[0];

        var field = typeof(ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
        string connectionStringToReplace = node.Attributes["connectionString"].Value;

        field.SetValue(connectionString, false);
        connectionString.ConnectionString = connectionStringToReplace;
        field.SetValue(connectionString, true);
    }
}

Este código recorre todas las Connection Strings, y si encuentra una en nuestro fichero de transformaciones, la reemplaza.

Finalmente, directivas de preprocesador

Las configuraciones de Build nos permiten fijar ciertas directivas a nuestros proyectos, y estas directivas se aplicarán en tiempo de compilación. De esta manera si alguna de las dos claves está presente se sustituirán, en caso de que ninguna esté presente no se realizará una sustitución.

#if TEAM1
    ReplaceConnectionStrings("TEAM1");
#elif TEAM2
    ReplaceConnectionStrings("TEAM2");
#endif

A continuación se pueden ver dos ejemplos para los diferentes equipos.

Capture_team1

Capture_team2

Conclusiones

Esta solución nos da una manera efectiva de poder depurar nuestro código sin afectar el ciclo de vida del fichero web.config, sus transformaciones, y el comportamiento por defecto. El código agregado solamente se ejecutaría en caso de que alguna de las dos directivas se cumpliese, con lo cual podemos tener diferentes configuraciones para diferentes equipos.

Reflection es una característica que nos permite sacar mucho más de lo que un objeto nos ofrece públicamente, a costa de un impacto en rendimiento y requerir un mayor conocimiento de lo que estamos haciendo, de ahí a que se use con moderación.

Enlaces adicionales

Probando módulos de NancyFx

En el artículo anterior de la serie de NancyFx hablábamos de diferentes tipos de respuesta así como el uso de plantillas en la plataforma. En este artículo veremos cómo crear pruebas unitarias para asegurarnos que nuestros módulos funcionan correctamente.

Creando nuestro primer test

Desde la documentación de Nancy se recomienda usar un proyecto diferente (es decir, un ensamblado diferente) para nuestros tests, ya que los módulos se descubren de manera automática por el «bootstrapper». Una vez creado nuestro proyecto, necesitamos agregar las siguientes referencias, via nuget o de manera manual:

  • NUnit (o XUnit, o el framework que deseemos)
  • Nancy
  • Nancy.Testing

Una vez tenemos las referencias, podemos crear nuestro primer test (que tiene el original nombre de RoutesTest.cs). Dicho test estará dividido en tres partes:

  • Arrange: Toda la preparación necesaria para poder llevar a cabo el test
  • Act: La ejecución de nuestro test
  • Assert: La fase de comprobación de los resultados
[Test ()]
public void TestPut ()
{
        //Arrange
	var bootstrapper = new DefaultNancyBootstrapper();
	var browser = new Browser(bootstrapper);

        //Act
	var result = browser.Put("/routes", with => {
		with.HttpRequest();
	});

        //Assert
	Assert.AreEqual (HttpStatusCode.OK, result.StatusCode);
	Assert.AreEqual ("Response with Put\n", result.Body.AsString());
}

Qué hace el test?

  • Arrange: Para este test el primer paso es crear nuestro Bootstrapper que descubrirá los módulos existentes, seguido de un Browser con el que simularemos las llamadas a nuestros módulos.

  • Act: Una vez tenemos el browser, el siguiente paso es generar la peticion, en este caso Put, a una ruta especificada.

  • Assert: Con el resultado de la ejecución, comprobamos el estado de la respuesta (en este caso 200 OK) y que el texto corresponda con lo que necesitamos, que es «Response with Put\n».

Una vez tenemos las referencias, recordemos qué contenía el módulo que queríamos probar:

public class Routes : NancyModule
{
	public Routes ()
	{
		Get["/routes"] = _ => "Response with GET\n";
		Post["/routes"] = _ => "Response with POST\n";
		Put["/routes"] = _ => "Response with Put\n";
                ...
	}
}

Podemos identificar claramente una línea donde el verbo PUT devuelve «Response with Put\n», con lo cual nuestro test es correcto

Probando una ruta errónea

Al igual que podemos probar rutas correctas, los test son igual de válidos para comprobar que el acceso a una ruta no autorizada o no implementada se controla correctamente, como sucede a continuación:

[Test ()]
public void TestNotFound ()
{
	//Arrange
	var bootstrapper = new DefaultNancyBootstrapper();
	var browser = new Browser(bootstrapper);

	//Act
	var result = browser.Delete("/routes", with => {
		with.HttpRequest();
	});

	//Assert
	Assert.AreEqual (HttpStatusCode.MethodNotAllowed, result.StatusCode);
}

En este caso, la ruta no está implementada, con lo cual el resultado será un error de tipo «Method Not Allowed».

Conclusiones

Con pocas líneas de código podemos comenzar a probar nuestros módulos de NancyFx, podemos probar la respuesta de los mismos, así como códigos de error. El sistema de test permite, adicionalmente, pasar parámetros (simulando el uso de un formulario) o navegar a través del DOM del HTML devuelto, si usamos un ViewEngine.

Balanceo de carga y escalabilidad con ASP.net, primer contacto

Una de los problemas a los que no he tenido la oportunidad de enfrentarme, de manera profesional, es manejar carga de servidores web, así que esta semana me he dispuesto a montar un sistema para balancear dos sitios ASP.net alojados en Azure. En este artículo veremos cómo ponerlo en marcha. Ha sido una aventura muy interesante, así que te invito a que sigas leyendo.

La arquitectura

Últimamente tenemos muchas capas de servicios que nos abstraen de la lógica necesaria para todo esto: los Azure Websites pueden escalar automáticamente, Azure proporciona su propio balanceador de carga a través del componente Traffic Manager, y Amazon tiene su Elastic Load Balancer.

Para este escenario, sin embargo, quería montarlo de manera un poco más artesanal, y esta es la arquitectura:

  • Dos Azure Websites diferentes, uno alojado en North Europe y otro en West US.
  • Una máquina virtual, con Linux (aquí meteremos el balanceador de carga) alojada en East Asia.

¿Por qué Linux? Linux tiene una gran importancia en la actualidad en el área de servidores. Llevaba tiempo sin trabajar con él y además parecía divertido combinar las diferentes plataformas.

Identificando las instancias

Un detalle que puede pasar desapercibido si vamos a nuget.org, es que en el pie de página identifican el servidor en el que estamos (You are on XXXX), con lo cual, si refrescamos la página, veremos otro servidor.

nuget

Para esta prueba sería muy útil saber qué servidor está sirviendo los contenidos, así que lo primero que haremos será crear un proyecto por defecto de ASP.net, y sustituir la acción Index de HomeController por:

public ActionResult Index()
{
    ViewBag.Title = "Home Page";
    ViewBag.ServerName = System.Environment.MachineName;
    return View();
}

Por otro lado sustituimos el código de Index.cshtml (la vista) por:

<div class="jumbotron">
    <h1>Load balancing testing</h1>
    <p class="lead">You are on @ViewBag.ServerName</p>
</div>

El resultado (en local) es este:

Lb_local

Una vez que tenemos nuestro sitio funcionando, el siguiente paso es subirlo a dos servidores diferentes, que son en este caso:

Cada máquina tiene un identificador diferente, de esta manera, cuando hagamos el balanceo de carga, podremos saber en qué máquina estamos.

Agregando el balanceador

Para el balanceador he optado por un Ubuntu Server 12 hospedado en una máquina virtual también en Azure. Una de las cosas que no debemos olvidar es abrir el puerto 80 para poder recibir tráfico normal de Internet (para este ejemplo dejamos de lado temas relacionados con HTTPS, certificados SSL y similares). Con Linux tenemos muchas opciones. Estuve probando las siguientes:

Fair

Es una opción muy sencilla para empezar, pero tiene un problema, ya que necesita un componente en cada máquina que queremos balancear, lo cual nos limita el radio de acción. Puede que en otra ocasión…

Balance

A diferencia de Fair, no necesita un componente en cada máquina, lo que era un plus. Sin embargo, el hecho de estar basado en comandos y no en configuración no me acababan de convencer. Finalmente, no pude hacerlo funcionar por problemas de puertos, permisos y otros.

HAProxy

HAproxy es, según he podido leer, el balanceador más completo y configurable. Funciona mediante un fichero de configuración y necesita direcciones IP fijas, ya que, según los ejemplos, está pensado para que los servidores a balancear estén en la misma red. Después de varios intentos, no conseguí que el balanceador funcionara, así que desistí.

Apache

Por último, descubrí que Apache tiene un módulo específico para proxy y balanceo, que se parecía bastante a lo que necesitaba. Aunque parezca matar moscas a cañonazos, al final resultó ser bastante fácil de configurar.

Instalando y configurando Apache

El primer paso es instalar Apache en nuestro sistema:

sudo apt-get install apache2

El siguiente paso es habilitar los componentes necesarios para el balanceador:

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer

Una vez tenemos activados todos los componentes, debemos escribir la configuración al final (justo antes de la directiva </IfModule>) de /etc/apache2/mods-available/proxy.conf:

<VirtualHost *:80>
ProxyRequests off
<Proxy balancer://mycluster>
    # WebHead1
    BalancerMember http://scaledemo7844.azurewebsites.net:80

    # WebHead2
    BalancerMember http://scaledemoamerica.azurewebsites.net:80

                Order Deny,Allow
                Deny from none
                Allow from all

    ProxySet lbmethod=byrequests

</Proxy>
        ProxyPass / balancer://mycluster/
</VirtualHost>

Finalmente reiniciamos el servicio:

sudo apache2 restart

Si todo ha ido bien (y espero que sí), podemos acudir a http://ubuntubalancer.cloudapp.net/ (actualmente offline), donde accederemos indistintamente a cualquiera de las dos instancias y podremos ver su identificador.

Conclusiones

En este artículo hemos visto cómo podemos escalar nuestra página y usar un balanceador intermedio para gestionar las peticiones. Es un primer paso y todavía quedaría mucho por hacer. En el próximo artículo de esta serie, veremos cómo compartir la sesión entre las diferentes páginas que componen nuestro pequeño cluster.

Enlaces Interesantes:

El framework 960gs para diseño web

Reconozco que soy un diseñador y/o maquetador bastante del montón (de la parte baja del montón), en comparación con los artistas que se ven actualmente en internet, así que recurro de manera bastante habitual a frameworks que me facilitan bastante la existencia.

960gs es un framework CSS que  fija el ancho de la web que estás desarrollando a 960px, (el ancho estándar para una web vista correctamente en un monitor de 1024x 768), y la tarea que realiza es bastante simple, establece dicho ancho, y divide la web en 12, 16 o 24 paneles que podemos emplear a nuestra voluntad.

Un ejemplo de uso sería el siguiente, tras agregar en el head de nuestra web la referencia al css, agregamos esta estructura, que nos divide la web en dos secciones, un grid de anchura 4 y otro de anchura 8. cada «unidad» en este caso tiene 60 px con un margen de 10px a cada lado, lo que nos permite jugar un poco con la estructura de nuestro sitio.

<div class="container_12">
    <div class="grid_4"></div>
    <div class="grid_8"></div>
</div>

Personalmente me parece extremadamente útil para poder maquetar la web y mantener cierta coherencia en el diseño, además, el resultado final es muy agradable para la vista.

Como detalle adicional, cuando nos descargamos el paquete tenemos además plantillas y plugins para photoshop/illustrator, además de un par de plantillas para hacer bocetos sobre papel directamente, que nos permitirán hacer nuestro sitio pensando en los 960px directamente en la fase de diseño y que nos sea más sencillo luego implementarlo.

Espero que os resulte interesante.

Más información: 960.gs

Jornadas Desarrollo Web UAM 2011, lo que he aprendido

Durante esta semana he tenido el privilegio de organizar, junto con algunos compañeros, las primeras jornadas de desarrollo web en la Escuela Politécnica Superior de la UAM. Lo que surgió como una idea fugaz para un par de días en período de exámenes (en Febrero), ha acabado siendo un evento de una semana con 5 ponencias, casi 10 horas, y 230 asistentes.

Ha sido algo intenso, porque mientras iba pasando el tiempo, el número de patrocinadores iba aumentando, algunos ponentes caían, otros surgían a última hora, y poco a poco las cosas se iban definiendo.

También ha sido agotador, si empezamos a sumar son varias horas de preparación, el inventario de material, las pruebas de sonido, vigilar el streaming desde la última fila, después del evento subir los materiales… pero me lo he pasado en grande.

Ver como en la charla de seguridad se te acaban las encuestas porque el numero de asistentes se sale de las gráficas, o como superamos las 30 personas en el livestream, es algo impresionante.

También he podido ser ponente (un viernes antes de semana santa, así que era fácil :P), y he aprendido muchísimo con ello, sobre cómo montar una presentación, preparar una demo (era mi primera charla técnica), tener la chuleta a mano y controlar los horarios.

He aprendido, sobre todo, lo importante que es tener un buen equipo, que esté motivado, y que de ideas, gracias chicos, sin vosotros no habría sido posible: Nacho, Moya, Carlos, Sara, Cris, Bea, Gioryo, Alvaro… y a mi familia, por estar ahí (literalmente hablando) en los eventos en los que era posible asistir.

Jornadas de desarrollo web UAM.net

La semana próxima organizamos en UAM.net las primeras jornadas de desarrollo web, hablaremos de un montón de cosas interesantes, desde seguridad hasta mvc pasando por redes sociales.

Tenemos ponentes de alta calidad (y yo.. que he conseguido colarme). Hablaré del patrón MVC que últimamente está en alza con las aplicaciones web y veremos un par de ejemplos prácticos usando .net y Ruby on Rails.

Tras el salto la lista de charlas de manera detallada:

Continuar leyendo «Jornadas de desarrollo web UAM.net»