Gestión de la Configuración de ASPNET 5 con Azure (y Docker!)

Quienes han leído mis anteriores artículos sobre el despliegue en Azure pensaran que uff, ya tenemos todo listo para buscarnos la vida hasta que Release Management de un soporte total a PaaS, pero la verdad es que la cosa tiende a evolucionar y hay que hacer pequeños ajustes de cara a los cambios que se anunciaron en el Build y que vienen con el Visual Studio 2015.

Esto a propósito de la posibilidad que nos ofrece Microsoft de desarrollar aplicaciones .Net que corran tanto en Mac como en Linux, una posibilidad totalmente disruptiva que es todo un ejemplo de los cambios que llegaron de la mano de Satya, pero claro, esto no implica que lo que hemos desarrollado hasta ahora en ASP.NET lo recompilamos y lo subimos a Linux y ya esta… no, claro que no, para lograr el objetivo Microsoft ha sacado una nueva edición del .NET Framework llamada Core, sobre la cual se ha desarrollado una nueva versión de ASP.NET (concretamente la 5) mas modular y ligera, sin las dependencias monolíticas usuales, aprovechando tendencias ya existentes en el mercado: Bower, Grunt, npm…

Genial, pero todo esto viene con un precio, el primero y para mi el mas doloroso es que esta nueva versión de ASPNET no incluye proyectos en WebForms (sniff :'( ), ha habido un reacomodo en algunas librerías (sobre todo de cara a lograr la integración entre MVC y WebAPI), desaparición de global.asax, en fin no doy mas detalle porque para eso mejor leer los artículos de José M. Aguilar que va cubriendo con detalle las cosas que van saliendo.

Pero dentro de todos los cambios habidos hay uno que nos afecta especialmente a quienes trabajamos en la gestión de entornos de trabajo y de despliegue, y es que por defecto al crear un proyecto nuevo ya no hay un archivo web.config, esto debido a que la gestión de configuración ya no esta centralizada en un archivo monolítico sino que puede estar dispersa a lo largo de un conjunto de archivos (de preferencia JSON) o de las variables de entorno del sistema, correspondiendonos a nosotros el elegir que archivos usaremos y en que orden de secuencia y prioridad se van sacando, y claro la forma de acceder a estas propiedades, que vimos anteriormente, han cambiado.

slots29

Si pues, ya no mas ConfigurationManager y similares…

Y por lado de la estructura de los archivos JSON seria de esta forma, mucho ojo a este config.json, lo usaremos mas abajo:

configjson

Ok, entonces ¿como hago para acceder a dichos valores desde mi código? Los detalles son explicados en este articulo, pero en corto vayamos manos a la obra y descubramos una forma optima para nuestros futuros desarrollos, así que primero creemos un nuevo proyecto Web:

 

Clipboard02

Nada fuera de lo común, pero ojo al siguiente paso, se tiene que elegir un proyecto con las nuevas plantillas y en este caso concreto he elegido “Web Site” que nos brinda alguna estructura ya hecha que a efectos de aprendizaje nos servirá bastante para explicar algunas cosas:

Clipboard03
Ahora revisemos la estructura de nuestro proyecto, si algo diferente ¿Que es Gulp? ¿Que es Bower? yo también estoy como ustedes pero nos tocara ir aprendiendo:Clipboard04

Ahora editaremos el código del HomeController.cs para que acceda a las variables de configuración, para lo cual agregaremos una referencia a Microsoft.Framework.ConfigurationModel; y editaremos la función (vinculada a una vista) public IActionResult About(), así:

Clipboard05

Como se puede ver el código es simple, instanciamos una nueva variable de tipo Configuration, para seguidamente indicarle que cargue el archivo config.json que ya vimos arriba y luego procese las variables de entorno, para luego recuperar el AppSetting llamado “DondeEstoy” (si, es el mismo de nuestro articulo anterior y ya verán porque), nótese el uso de los “:” para separar la sección del key, así que si quisiera acceder a llave “ClaveAdicional” el código seria config.Get(“AppSettings:ClaveAdicional”).

Probemos la aplicación:

Clipboard08

Upss, me olvide pasarles mi vista About, nada del otro mundo, ha sido una ligera modificación sobre la que vino por defecto, siendo que lo que realmente importa es lo que estamos devolviendo desde el Controlador en el ViewBag.Message.

Ok, diríamos ahora, ya sabemos como acceder a las variables de configuración de ASPNET 5, la vida continua, pues no… aun tenemos margen para hacer algo mas eficiente, primero que nada, cuestionemonos si tiene sentido hacer un new Configuration(); y todos los “Add” respectivos cada vez que necesitemos acceder a dichas variables ¿no sería mejor hacer esa carga una única vez?, para ello tenemos dos facilidades que mezclaremos para lograr dicha mejora.

Como primer paso debemos abrir el archivo Startup.cs (que digamos que es un heredero de Global.asax), en este punto nos viene muy bien el no haber elegido un proyecto vacio, puesto que ya contamos con un código muy funcional para lo que necesitemos, en este sentido revisemos el código del constructor:

Clipboard09

Genial, ya teníamos un objeto Configuration totalmente listo para usar, ahora lo que toca es hacer que este disponible para nuestros controllers, para esto debemos entender que ASPNET5 es totalmente modular y que nos corresponde a nosotros acceder a la función public void ConfigureServices(IServiceCollection services) para definir que módulos (¡incluyendo el motor de MVC!) estarán disponibles dentro de nuestra aplicación, en este caso VS nos ofrece EntityFramework e Identity, a los cuales sumaremos la linea de código services.AddSingleton(_ => Configuration); para lograr el efecto deseado :

Clipboard10

Esa sencilla linea habilita la Inyección de Dependencias facilitando el acceso al objeto Configuration ya instanciado desde nuestros controllers, para verificarlo regresaremos a HomeController.cs, para agregarle una propiedad llamada _config de tipo Configuration, y un constructor a la clase, así:

Clipboard11
Toca ahora modificar nuestro About() para remover el new y los Add, quedando mucho mas ligero así:
Clipboard12

Lo probamos:

Clipboard13

Un paso mas allá…

Ok, ¿con eso terminamos? pues no, ya que aun no vemos Azure, concretamente lo que no hemos hecho aun es acceder a los Application Settings que gestionan las WebApps de Azure como vimos con detalle hace unas semanas, en este caso los cambios de ASPNET 5 hace que busquemos alternativas al clásico ConfigurationManager.AppSettings que venimos usando desde hace años, y la verdad es que no es tan complicado, bastaría con invocar _config.Get(“APPSETTING_DondeEstoy”);  para acceder a la variable “DondeEstoy” que hemos definido en nuestra WebApp de Azure y sus respectivas ranuras:

slots32

Antes de proseguir debo decir que el acceder a “DondeEstoy” via la sintaxis “APPSETTING_DondeEstoy” se debe a que desde el principio (aun antes de ASPNET 5) las variables de Configuración de Aplicación de Azure han estado disponibles como variables de entorno, esto es lo que ha permitido que dichos valores sean accesibles por otros lenguajes de programación como PHP o Python (*), adicionalmente para poder acceder a las variables de entorno en general es necesario que nuestro objeto Configuration haya llamado al método AddEnvironmentVariables() lo cual hemos hecho desde el inicio de nuestro ejemplo. En todo caso el tema de la nomenclatura (“APPSETTING_DondeEstoy” vs “AppSettings:ClaveAdicional” ) nos pone en un pequeño inconveniente si queremos usar archivos de configuración en modo local y variables gestionadas por Azure cuando despleguemos nuestra aplicación a la nube, pero nada que una clase Helper no pueda arreglar.

Así que venga la clase (**):

static public class HelperConfig
    {
        public static string GetAppSettings(IConfiguration config, string key)
        {
            var localKey = "AppSettings:" + key;
            var envKey = "APPSETTING_" +key;
            return config.Get(envKey) ?? config.Get(localKey);
        }
    }

Como puede verse la lógica es muy sencilla, primero intenta recuperar el valor de la variable de entorno con su sintaxis propia “APPSETTING_ y si no la encuentra busca lo que debería estar en alguno de los archivos de configuración (recordemos que pueden cargarse varios archivos) con su sintaxis “AppSettings:”.

Ahora solo resta usar la clase desde nuestro controller:

public IActionResult About()
 {
 var DondeEstoy = HelperConfig.GetAppSettings(_config, "DondeEstoy");
 if (String.IsNullOrEmpty(DondeEstoy))
 ViewBag.Message = "No se pudo recuperar";
 else ViewBag.Message = "Nuestro entorno con DI y Helper es " + DondeEstoy;
return View();
 }

Como podemos ver la invocación permite olvidarnos de prefijos y sintaxis, simplemente pasar el nombre de la variable, ya el Helper por detrás se encarga de agregarlas por su cuenta.

Para terminar con esta prueba desplegaremos nuestra aplicación al web slot de qa que vimos en nuestro post anterior, a fin de no tener que provisionar un nuevo entorno ni crear nuevamente las variables, pero al hacerlo deberemos asegurarnos de desmarcar la opción "Publish using PowerShell script" como se nos indica aquí, hecho esto veamos que pasa….

Clipboard15

Listo misión cumplida, tenemos un modo de trabajar que nos vale tanto para los archivos como para las variables gestionadas desde Azure, pero…¿qué pasaría si lo probamos desde Docker? Docker… si no has estado viviendo debajo una roca en el ultimo año debes saber que Docker es una tecnología que permite gestionar contenedores con lo mínimo que sea necesario para correr tu aplicación, de momento solo esta disponible en Linux (pero el soporte en Windows ya esta en camino), así que el reto seria crear y desplegar un contenedor hacia un entorno Linux, para esto podemos usar Azure y…. Visual Studio como lo explica con detalle Scott Hanselman, para complementar lo mencionado por Scott debo decir que en esta etapa me fue complicado hacer que el despliegue se efectuara en alguno de los entornos ya venia preparando para practicar con Docker, así pues lo mejor es crear el entorno directamente desde Visual Studio y olvidarnos del asunto, funcionara perfectamente (aunque tarda un poco, ya que se están subiendo mas de 28MB a nuestra MV que cuenta con el Docker daemon):

Clipboard16

Nótese que dice “Local”, esto es porque este contenedor ha sido desplegado en un entorno totalmente diferente al de la WebApp que vimos antes, para empezar es un Linux, y no hemos hecho nada especial para configurar variables de entorno, solo hemos empaquetado y desplegado, por lo que nuestro Helper accedera a los valores contenidos dentro del archivo config.json que fue empaquetado en el despliegue, lo esperado 🙂

Para terminar debo mencionar que en este caso he hecho los despliegues desde el IDE de Visual Studio, aun no he involucrado a mi querido Visual Studio Online para integrar esos despliegues (publish) desde un proceso integro con las nuevas tecnologías de Build, espero cubrir eso en próximas entregas, pues creo que se podria ganar una ventaja en los tiempos de subida de los contenedores de Docker si se hace desde Visual Studio Online (ubicado en USA) directamente hacia las MV de Azure. ¡Espero sus comentarios¡

Accessing configuration variables using Dependency Injection in MVC 6
(*) Si quisiéramos desplegar una WebApp usando PHP y acceder a las variables gestionadas por Azure necesitaríamos una linea como esta $dondeestoy = getenv(‘APPSETTING_DondeEstoy’);, demostrando que esta ventaja no es exclusiva de .Net

(**) Actualización (27/06/2015): Si bien es muy probable que luego actualice el Helper para ayudarme en la gestión de Connection Strings, por un momento pensé si seria mejor simplificar las cosas usando Métodos de Extensión sobre la clase Configuration, me di cuenta que seria agregar demasiada complejidad puesto que el método “agregado” a Configuration no sería parte de la Interfaz IConfiguration que es la que se tiene que usar si queremos trabajar con la Inyección de Dependencias; que si, que podría agregar otra Interfaz para seguir en ese plan pero en este caso seria una mayor complejidad por lo que mejor nos quedamos con el Helper y seguimos pasando IConfiguration como parámetro al método estático ¿no creen? 😉

Agregue un comentario

Su dirección de correo no se hará público.

Time limit is exhausted. Please reload the CAPTCHA.