Archivo de la categoría: Tutoriales

Truco del viernes: Solucionando problemas con iOS y Exchange

Un error que podemos tener cuando configuramos un nuevo dispositivo con Exchange es que, sin tener ningún error ni señal de fallo, no recibamos ningún e-mail. La solución pasa por acceder a nuestro Outlook Web Access y comprobar el número de dispositivos conectados.

Para ello, una vez iniciada sesión podemos desplegar el menú, y seleccionar Todas las opciones:

allOptions

Una vez seleccionadas, vamos a la opción de Teléfono, borramos los terminales más antiguos que no usemos, y podremos “hacer hueco” para nuestro nuevo dispositivos

set_phone

Una vez eliminados los terminales antiguos, el nuevo dispositivo comenzará a sincronizar.

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

Eliminando acoplamiento en un controlador ASP.net MVC

Cuando desarrollamos aplicaciones web podemos caer en el error de dar demasiada responsabilidad a nuestros controladores lo que nos puede traer problemas en el futuro al intentar refactorizar ese código.

En este artículo veremos cómo partiendo de una acción donde la carga de datos se realiza desde el propio controlador podemos reducir el acoplamiento a la manera de guardar los datos. Con ello conseguiremos mejorar la reusabilidad y legibilidad del mismo, así como permitir realizar pruebas unitarias o cambiar componentes de manera sencilla.

Código inicial

Nuestro primer código es un simple controlador con un método que lista un catálogo de libros. Este controlador contiene un método que lista un catálogo de libros. Usaremos directamente sentencias SQL contra una base de datos LocalDB.

public class HomeController : Controller
{
    public ActionResult Books()
    {
        List<Book> books = new List<Book>();
        SqlConnection connection = new SqlConnection(@"Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\Books.mdf;Integrated Security=True");
        SqlCommand cmd = new SqlCommand();
        SqlDataReader reader;

        cmd.CommandText = "SELECT Title,Author FROM [Table]";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = connection;

        connection.Open();

        reader = cmd.ExecuteReader();
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                books.Add(new Book
                {
                    Title = reader.GetString(0),
                    Author = reader.GetString(1)
                });
            }
        }

        connection.Close();
        return View(books);
    }
}

El resultado es una lista de objetos de tipo Book, que es un POCO (Plain Old C# Object) que contiene dos campos, Title y Author. La principal desventaja, y es la que intentaremos resolver a lo largo de este artículo, es que presenta un fuerte acoplamiento con el gestor de base de datos, y si cambiamos a MySQL o MongoDB, por poner un ejemplo, nos veríamos obligados a reescribir la esta implementación.

Primera aproximación, patrón Repository

El objetivo de un patrón Repository es crear una capa de abstracción entre la lógica de negocio (representada en este caso por el controlador) y la lógica de base de datos (representada por el acceso a SQL). Dicha capa de abstracción nos permite probar nuestro controlador de manera independiente a los datos y a la manera de almacenar los mismos.

La implementación de un patrón repository la podemos realizar usando una interfaz y su correspondiente implementación:

public interface IBookRepository
{
    List<Book> GetAllBooks();
}

public class BookRepository : IBookRepository
{
    private SqlConnection _connection;
    private string _connectionString = @"Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\Books.mdf;Integrated Security=True";
    public BookRepository()
    {
        _connection = new SqlConnection(_connectionString);
    }

    public List<Book> GetAllBooks()
    {
        List<Book> books = new List<Book>();
        SqlCommand cmd = new SqlCommand();
        SqlDataReader reader;

        cmd.CommandText = "SELECT Title,Author FROM [Table]";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = _connection;

        _connection.Open();

        reader = cmd.ExecuteReader();
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                books.Add(new Book
                {
                    Title = reader.GetString(0),
                    Author = reader.GetString(1)
                });
            }
        }

        _connection.Close();
        return books;
    }
}

El código de la implementación es el mismo, ya que seguimos usando SQL para el acceso a datos, sin embargo hemos creado una abstracción que nos permite realizar pruebas unitarias y dejar los detalles de la implementación del acceso a datos al Repositorio.

El código del controlador se reduce al código siguiente:

public class HomeController : Controller
{
    private IBookRepository _bookRepository;

    public HomeController()
    {
        _bookRepository = new BookRepository();
    }

    public ActionResult Books()
    {
        return View(_bookRepository.GetAllBooks());
    }
}

Pese a que el repositorio se inicia desde el controlador, podríamos usar una solución de inyección de dependencias como Ninject o crear nuestra propia solución para que la inicialización de las dependencias se realice fuera del mismo.

Reduciendo acoplamiento en nuestro Repositorio

Una vez tenemos separados el controlador y el repositorio, tenemos varias alternativas para mejorar la legibilidad y la facilidad de uso del código

LINQ to SQL

Usar LINQ to SQL nos permite realizar un mapeado directo entre nuestra tabla y una clase, así como una capa de abstracción adicional sobre nuestro repositorio.

public class LinqToSqlBookRepository : IBookRepository
{
    private DataContext _db;
    private string _connectionString = @"Data Source=(LocalDb)\v11.0;AttachDbFilename=|DataDirectory|\Books.mdf;Integrated Security=True";
    public LinqToSqlBookRepository()
    {
        _db = new DataContext(_connectionString);
    }

    public List<Book> GetAllBooks()
    {
        Table<Book> book = _db.GetTable<Book>();
        return book.ToList<Book>();
    }
}

Para que el mapping funcione correctamente, hemos de decorar nuestro modelo con los nombres de las columnas equivalentes:

[Table(Name = "Table")]
public class Book
{
    [Column(Name = "Title")]
    public string Title { get; set; }
        
    [Column(Name = "Author")]
    public string Author { get; set; }
}

Esta aproximación nos da una capa de abstracción mayor, comprobación de tipos en tiempo de compilación, capacidad de debug, y facilidad de acceso, a costa de perder algo de flexibilidad y rendimiento.

Conclusiones y pasos adicionales

LINQ to SQL no es la única manera de agregar abstracciones a nuestros datos, también podemos usar sistemas más complejos como Entity Framework, NHibernate, o Massive, siendo este el último que he descubierto y al que espero dedicarle pronto un artículo. Cada capa de abstracción, como decíamos anteriormente, nos permite desacoplar el código que estamos usando, aumentar su facilidad para ser probado, y su legibilidad, a la vez que cumplimos con principios como el de responsabilidad simple.

El proyecto termina con 3 capas diferenciadas:

  • Capa de presentación (Controlador)
  • Capa de datos (Repositorio)
  • Capa de persistencia (SQL)

Podemos agregar capas de lógica de negocio (si queremos realizar validaciones sobre los datos antes de que lleguen a la base de datos) o de servicio (si tenemos varias interfaces para nuestra aplicación). Cada capa aumenta la complejidad de nuestra aplicación pero también aumenta nuestra capacidad para limitar las responsabilidades de cada clase.

Puedes ver el código completo en github

Lecturas adicionales

Extra: Código de creación de la base de datos y datos de ejemplo

El código de creación de la base de datos y de los datos de ejemplo también es muy simple (SQL)

CREATE TABLE [dbo].[Table]
(
    [Id] INT NOT NULL PRIMARY KEY IDENTITY, 
    [Title] NVARCHAR(50) NULL, 
    [Author] NVARCHAR(50) NULL
)
INSERT INTO [dbo].[Table] (Title,Author) VALUES ('Hamlet', 'William Shakespeare')
INSERT INTO [dbo].[Table] (Title,Author) VALUES ('El Ingenioso Hidalgo Don Quijote de la Mancha', 'Miguel de Cervantes y Saavedra')
INSERT INTO [dbo].[Table] (Title,Author) VALUES ('La divina comedia', 'Dante Alighieri')

Agregando múltiples idiomas a una aplicación de Windows Store con Javascript y Angular

**Read this article in English here

El pasado jueves tuve la ocasión de asistir a una mesa redonda organizada por el grupo MSCoders relacionada con la localización de aplicaciones en entornos web.

Tras el evento, uno de los temas que más llamó mi atención fue la localización en la parte Javascript, y como tenía una aplicación ya en la Windows Store que usaba ese lenguaje, me pareció interesante intentar localizarla para poder ofrecerla en otros mercados.

Detectando el idioma del sistema

Para una aplicación Windows 8 el primer paso es detectar el idioma del sistema, y eso lo podemos hacer de la siguiente manera:

var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
var language = culture.substr(0, 2);

En este caso tomaremos la lista de lenguajes preferentes, y de esa lista obtendremos el primer elemento. Para poder cambiar el orden de los elementos y probar los diferentes lenguajes, podemos cambiarlo en el panel de control:

Captura de pantalla 2014-09-15 00.29.43

El resultado se muestra en formato cultura, es decir, español de España se mostraría como es-ES, e inglés de EEUU se mostraría como en-US. Para nuestro ejemplo inicial, solamente necesitamos el idioma, con lo cual hacemos un substring y obtenemos en o es respectivamente.

Agregando angular-translate

Angular-translate es un módulo de angular que nos permite definir de manera las claves y los valores para cada lenguaje, y realizar la traducción en función de la configuración. Para instalarlo, si no contamos con gestores de paquetes como bower, es tan sencillo como descargar el último paquete de github y agregarlo a nuestro proyecto.

Una vez instalado, tendremos que agregarlo a la configuración de nuestra app, en el apartado en el que definimos módulos externos en este caso pascalprecht.translate:

var karmacracyApp = angular.module('karmacracyApp', [
    'ngRoute',
    'karmacracyControllers',
    'pascalprecht.translate'
]);

Finalmente, configuramos los diferentes idiomas usando el código que hemos visto anteriormente para seleccionar un idioma acorde con el sistema operativo:

karmacracyApp.config(['$translateProvider', function ($translateProvider) {
    $translateProvider.translations('en', {
        'LOGIN': 'Log in',
        'LOGIN_USERNAME': 'User name',
        'LOGIN_PASSWORD': 'Password'
    });

    $translateProvider.translations('es', {
        'LOGIN': 'Inicia sesión',
        'LOGIN_USERNAME': 'Nombre de usuario',
        'LOGIN_PASSWORD': 'Contraseña'
    });

    var culture = Windows.System.UserProfile.GlobalizationPreferences.languages[0];
    var language = culture.substr(0, 2);

    $translateProvider.preferredLanguage(language).fallbackLanguage('en');
}]);

Además, en caso de que el sistema tenga un idioma no controlado, estableceremos el inglés por defecto.

Uso

Para acceder a nuestras claves no necesitamos realizar ninguna configuración adicional en nuestros controladores, solamente necesitamos modificar nuestras vistas para que accedan a la información localizada:

<span class="loginMessage">{{ 'LOGIN' | translate }}</span>

En caso de que tengamos el idioma establecido a inglés (o a cualquier idioma no controlado), se mostrará el siguiente resultado:

en

En caso de que el idioma esté establecido a español, el resultado será diferente:

es

Como se ve, es una manera sencilla de poder localizar nuestras aplicaciones en javsacript para Windows Store

Conclusiones y pasos adicionales

Angular-translate proporciona una manera sencilla de localizar textos en nuestras vistas, por otra parte, Windows proporciona, a través de su API, la información que necesitamos para saber en qué idioma mostrar una aplicación.

Un paso adicional sería localizar controles winjs como los botones, que funcionan de manera diferente. Por otra parte se podría sacar un fichero de traducciones fuera de la configuración en formato json, útil si queremos distribuir esos ficheros para su procesamiento.

Enlaces adicionales

Cacheando con Varnish un proyecto ASP.net MVC5

Una caché, por definición, es un almacenamiento a corto plazo de información y con una altísima velocidad de acceso, permitiéndonos mejorar el rendimiento de nuestros sistemas. En el caso de la web podemos diferenciar dos tipos:

  • Caché de cliente, que se hace en el navegador y que nos permite no tener que traer de internet imágenes o ficheros de estilo una vez hemos obtenido una copia.
  • Caché de servidor, que guarda una copia de manera temporal para evitar sobrecarga de nuestro servidor de aplicaciones.

Esto último tiene una justificación, y es que el acceso a las aplicaciones (PHP, ASP.net, Ruby) es un proceso relativamente lento que suele estar acompañado de un acceso a una base de datos, una aplicación de la lógica de negocio y la composición final de la página.

Existen diferentes sistemas y tipos de caché, como Memcached o Redis. En este artículo hablaremos de Varnish, un proyecto open-source que se instala sobre sistemas UNIX (aunque tiene soporte para Windows mediante Cygwin).

La principal peculiaridad que tiene este sistema, además de sus casi-infinitas opciones de configuración, es que permite dividir nuestra página en trozos y cachearlos de manera independiente, en función del intervalo de actualización de los mismos, un proceso conocido como Edge Side Includes (ESI) y que veremos a lo largo del artículo.

Instalación y configuración

La instalación de Varnish es un proceso tan simple como brew install varnish si usamos homebrew en mac. Una vez instalado, para ejecutarlo usamos el siguiente comando:

sudo /usr/local/opt/varnish/sbin/varnishd -n /usr/local/var/varnish -f /usr/local/etc/varnish/default.vcl -s malloc,1G -T 127.0.0.1:2000 -a 0.0.0.0:8082

pasando como parámetros las siguientes opciones:

  • Ruta del fichero de configuración
  • Puerto de registro de log
  • Máximo consumo de RAM
  • Puerto de escucha (en nuestro caso el 8082)

El fichero de configuración default.vcl, para nuestro ejemplo, contiene las siguientes directivas:

backend default {
    .host = "127.0.0.1";
    .port = "8087";
}

sub vcl_recv {
	if (req.http.cookie) {
		unset req.http.cookie;
	}
}

sub vcl_fetch {
	
	if (req.url == "/layout/*") {
       set beresp.ttl = 2m;      /* Sets a one minute TTL on        */
    } else {
       set beresp.do_esi = true; /* Do ESI processing               */
       set beresp.ttl = 30s;     /* Sets the TTL on the HTML above  */
    }
    unset beresp.http.set-cookie;
}

sub vcl_deliver {

    if (obj.hits > 0) {
            set resp.http.X-Cache = "HIT";
    } else {
            set resp.http.X-Cache = "MISS";
    }
}

Los comandos que hemos implementado son:

  • backend: Permite especificar qué servidores estarán funcionando al otro lado, se puede especificar una IP o un nombre DNS.

  • vcl_recv: En el caso de ASP.net, se genera una cookie de sesión al acceder al sitio, lo cual no siempre es lo deseable. En este ejemplo se borran todas las que provienen del servidor, de manera que se pueda cachear la página.

  • vcl_fetch: Se ejecuta cuando obtenemos un elemento desde el servidor, y en este caso especificamos que si estamos solicitando algún elemento de “layout” como el menú o la cabecera, ha de cachearlo durante 2 minutos, en caso contrario configuramos para que haga el procesamiento ESI (es decir, que intente unir los pedazos) y cachee el conjunto durante 30 segundos.

  • vcl_deliver: Nos permite editar la cabecera HTTP para saber si ha podido resolver el elemento en la caché o si ha tenido que acceder al servidor de aplicaciones.

Cacheando voy, cacheando vengo:

En este ejemplo hemos usado una plantilla de bootstrap que tiene el siguiente aspecto dentro de una aplicación prácticamente vacía en MVC5:

goal

Lo primero que podemos apreciar son tres zonas claramente diferenciadas, una cabecera, una barra lateral, y, finalmente, un área de contenido principal.

Estructura

Para mostrar la página, empleamos un controlador muy sencillo que genera una espera de 3 segundos para simular carga del servidor y almacena la fecha de generación en el ViewData:

public class HomeController : Controller
{
	public ActionResult Index ()
	{
		ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
		System.Threading.Thread.Sleep (3000);
		return View ("Index");
	}
}

De manera adicional y para poder aplicar ESI, necesitaremos dividir nuestra vista en tres vistas diferentes que correspondan a acciones diferentes del controlador, que serán:

  • / <- que genera el contenido de la página
  • /layout/header <- que genera la cabecera
  • /layout/sidebar <- que genera la barra lateral

Las dos acciones del controlador Layout son idénticas, como muestra el código:

public class LayoutController : Controller
{
    public ActionResult Sidebar()
    {
		ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
		System.Threading.Thread.Sleep (3000);
        return View ("Sidebar");
    }

	public ActionResult Header()
	{
		ViewData ["now"] = String.Format ("{0:HH:mm:ss}", DateTime.Now);
		System.Threading.Thread.Sleep (3000);
		return View ("Header");
	}
}

Nótese que por cada acción hemos agregado otros 3 segundos de retraso, teniendo un total de 9 segundos en el caso peor.

Para poder activar el ESI en las vistas, usamos etiquetas HTML que tienen el siguiente formato:

<esi:include src="/layout/sidebar" onerror="continue"/>

Además, a modo de prueba, podemos agregar algo de código javascript que se ejecute una vez cargada la página para apreciar la diferencia entre hora de generación y hora de renderización. Si todo ha ido bien, cuando carguemos por primera vez nuestra caché, se encontrará vacía, y sufriremos la espera de los 9 segundos.

En la imagen, G muestra la hora de generación en el servidor y R la hora de renderización en el cliente:

miss

Si volvemos a acceder, comprobaremos que el contenido de la página ya se ha cacheado, que las fechas de generación y de ejecución distan más de tres segundos, y que el inspector nos muestra el tiempo total de acceso a la página (700 milisegundos aprox):

hit

Conclusiones

Ha sido una prueba de concepto muy interesante, el hecho de descubrir que podemos tener nuestra web dividida en secciones con cachés diferentes nos da mucha movilidad al hacer portales con información dinámica. Varnish es una herramienta suficientemente compleja para ser tomada en serio y requiere que estudiemos a fondo sus características antes aplicarla a un sistema en producción.

Lecturas adicionales y enlaces

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 = &quot;Home Page&quot;;
    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: