Después de bastante tiempo trabajando en Azure, y como he estado optimizando el coste de Azure, voy a escribir algunos posts sobre cómo ahorrar.

Un escenario típico es el tener un front-end en web-role y unos procesos que ejecuten tareas en worker-roles (p.ej. una aplicación CQRS). Además, para poder disponer de alta disponibilidad se ha de tener al menos 2 instancias de cada, lo que nos lleva a tener 4 o más instancias!!! => $$$

Por ello, una opción es ejecutar tareas en el mismo web-role y así ahorrar in$tancia$.

Implementación de la llamada a la tarea

Para empezar mostraremos cómo empezar… Aunque es una opción no muy documentada, es posible ejecutar tareas en un web-role como si de un worker-role se tratase. Para implementar esto, simplemente hay que crear en el proyecto/aplicación web una clase que herede de RoleEntryPoint como haríamos en un worker role:

    public class WebRole : RoleEntryPoint
    {
        public override void Run()
        {
            while (true)
            {
                ...
            }
        }

        public override bool OnStart()
        {
            ...

            return base.OnStart();
        }
    }

Hasta aquí todo fácil y funciona perfectamente. En la misma instancia se ejecuta la aplicación web y el código que hemos implementado en la clase especializada de RoleEntryPoint. Esto es muy fácil, pero ahora es cuando vienen los problemas si las tareas que ejecutaba el worker-role hacia cosas un poco complejas.

Si queremos aprovecharnos de las bondades de Full IIS, ¿qué sucede si desde la especialización de RoleEntryPoint queremos acceder al fichero de configuración web.config y obtener la cadena de conexión a la BD con ADO.Net, Entity Framework, …? #FAIL

Web.config / WaIISHost.exe.config

Si pretendemos usar el Full IIS que implementa Azure, desde el código RoleEntryPoint no se puede acceder al fichero web.config para obtener cualquier tipo de dato como las cadenas de conexión o cualquier setting. Esto sucede porque cuando se aloja la aplicación web en Full IIS esta se ejecuta en el proceso w3wp.exe de IIS como siempre, mientras que el RoleEntryPoint se ejecuta en un otro proceso diferente llamado WaIISHost.exe.

Para poder leer las opciones de configuración desde el proceso WaIISHost.exe, estas deben estar definidas en otro fichero (WaIISHost.exe.config) que es el que será accedido por la aplicación como si fuera un web/app.config. Para poder ser hospedado en el destino, este fichero tendrá que tener la propiedad del fichero “Copy to Output Directory” = “Copy Always”.

ACTUALIZACIÓN: A partir de la versión 1.8 de Azure
el nombre del fichero config que se tendrá en cuenta en la ejecución del proceso
WaIISHost.exe es “<nombre aplicación web>.dll.config” y no el
WaIISHost.exe.config que se describe en este post 😉

<?xml version="1.0"?>
<configuration>
    <connectionStrings>
        <add ... />
    </connectionStrings>
</configuration>

Una vez ya tenemos esto implementado podemos darle a F5 y bieeeeen. Ya funciona, pero vamos a dar un paso más. ¿qué sucede si tenemos los ficheros Web.Debug.config, Web.Release.config, … para adaptar el fichero de configuración a los diferentes entornos? Esto mismo no puede ser implementado para este config #FAIL

Transformaciones MSBuild

Al fichero WaIISHost.exe.config no se le pueden aplicar transformaciones tan fácilmente como al web.config, pero existe una forma de hacer algo similar automáticamente añadiendo tareas MSBuild a mano en el fichero de proyecto.

Primero crearemos un fichero llamando WaIISHost.exe.transformation.config donde guardaremos las transformaciones necesarias para convertir el web.config al WaIISHost.exe.config (en este ejemplo he eliminado los 4 elementos que no necesitaba, pero se puede modificar, añadir o eliminar cualquier cosa como indica http://msdn.microsoft.com/es-es/library/dd465326.aspx).

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
    <system.web xdt:Transform="Remove"/>
    <system.serviceModel xdt:Transform="Remove"/>
    <system.webServer xdt:Transform="Remove"/>
    <appSettings xdt:Transform="Remove"/>
</configuration>

Después tenemos que cerrar el Visual Studio y abrir el proyecto web *.csproj con el notepad (#OrgulloBackend de @davidsb) y añadimos el siguiente código al xml:

<Project ...>
  ...
  <Target Name="BeforeBuild">
    <Message Importance="high" Text="--- Transformando $(MSBuildProjectDirectory)WaIISHost.exe.config ---" />
    <TransformXml Source="$(MSBuildProjectDirectory)Web.config" Transform="$(MSBuildProjectDirectory)Web.$(Configuration).config" Destination="$(MSBuildProjectDirectory)WaIISHost.exe.temp" />
    <TransformXml Source="$(MSBuildProjectDirectory)WaIISHost.exe.temp" Transform="$(MSBuildProjectDirectory)WaIISHost.exe.transformation.config" Destination="$(MSBuildProjectDirectory)WaIISHost.exe.config" />
    <Delete Files="$(MSBuildProjectDirectory)WaIISHost.exe.temp" />
    <Message Importance="high" Text="--- Transformado $(MSBuildBinPath)WaIISHost.exe.config ---" />
  </Target>
  ...
</Project>

En él pretendemos además de mostrar mensajes para ir informando:

  • Añadir el UsingTask para referenciar Microsoft.Web.Publishing.Tasks.dll, y así poder ejecutar las tareas TransformXml.
  • Ejecutar 3 pasos para:
    • Transformar el fichero Web.config, según el Build Configuration “Debug, Release, …” asociado y deja el resultado en el temporal WaIISHost.exe.temp. Para ver más tipos de tareas puedes ver http://msdn.microsoft.com/en-us/library/7z253716.aspx.
    • Transformar el fichero temporal anterior con las transformaciones guardadas en WaIISHost.exe.transformation.config para convertir el web.config en WaIISHost.exe.config.
    • Eliminar el fichero temporal.

Y con esto, cada vez que compilemos el proyecto se reemplazará el fichero WaIISHost.exe.config con el Web.config modificado por las dos transformaciones: la que corresponde según el Build Configuration, y la explicita para limpiar y/o ampliar el fichero destino.

Jerarquizando un poco

Para que no aparezcan demasiados ficheros en el proyecto, yo recomiendo volver a editar el fichero *.csproj del proyecto y editar el tag xml que tiene el WaIISHost.exe.transformation.config para que quede como sigue:

<Project ...>
  ..
  <ItemGroup>
    ...
    <Content Include="WaIISHost.exe.transformation.config">
      <DependentUpon>WaIISHost.exe.config</DependentUpon>
    </Content>
    ...
  </ItemGroup>
  ...
</Project>

De esta forma aparecerá en el Solution Explorer como un nodo anidado dentro de WaIISHost.exe.config.

Screenshot

Espero que este post sea de utilidad.

Au