![ASP.NET Core ASP.NET Core]()
Sin duda, entre las
mejoras incluidas en ASP.NET Core 2.2 destaca especialmente
el nuevo modelo de hosting in-process para IIS, que promete cuadriplicar el rendimiento prácticamente sin hacer ningún cambio en nuestras aplicaciones, siempre que las ejecutemos en un entorno Windows/IIS.
Como recordaréis, se trata de una mejora en el módulo ASP.NET Core (ANCM) que hace que las aplicaciones se ejecuten directamente dentro del
worker del servidor web (w3wp.exe), evitando las llamadas internas producidas cuando IIS actuaba como un mero
proxy inverso.
Por verlo gráficamente, el siguiente diagrama muestra la arquitectura tradicional de un sistema ASP.NET Core funcionando sobre IIS. En el modelo
out-of-process utilizado hasta ASP.NET Core 2.1, cada petición realizada desde el exterior era capturada por IIS, que a su vez lanzaba una petición HTTP local con los mismos contenidos hacia Kestrel, que era quien la procesaba ejecutando nuestro código. La respuesta generada desde Kestrel era enviada de vuelta a IIS como respuesta a la petición, quien la retornaba al agente de usuario:
![ASP.NET Core out-of-process]()
En este modelo de funcionamiento, por tanto,
cada petición HTTP entrante generaba otra petición HTTP interna que, aunque estuviera dentro de la misma máquina, imponía una penalización importante en el rendimiento de las aplicaciones.
El
hosting in-process, aparecido con ASP.NET Core 2.2, cambia las reglas del juego eliminando esas peticiones HTTP internas y los retardos que implicaban, lo que ha posibilitado el espectacular incremento en el rendimiento que su uso ofrece. Ahora, el módulo para IIS ejecuta internamente la aplicación y se comunica con ella de forma directa:
![Hosting in-process]()
Este es el modo por defecto de los nuevos proyectos ASP.NET Core creados usando las plantillas estándar, algo que podemos claramente ver si echamos un vistazo al archivo
.csproj
de un proyecto recién creado, ya sea desde Visual Studio o desde .NET Core CLI:
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
Si tenéis un proyecto ASP.NET Core 2.1 o anterior y lo actualizáis a 2.2, tendréis que añadir el elemento <AspNetCoreHostingModel>
de forma manual para usar este modelo de ejecución.
Sin embargo, estas mejoras espectaculares no son gratis del todo, y creo que
es interesante conocer un poco mejor lo que implica asumir este nuevo modo de funcionamiento de nuestras aplicaciones y responder a algunas preguntas que podríamos hacernos por el camino.
¿Qué implica usar el modo in-process?
Pues como casi todo en la vida, asumir este nuevo modo de funcionamiento tiene sus implicaciones y, a veces, contraindicaciones ;)
Por ejemplo, una consecuencia directa es que cuando usamos este modo desde Visual Studio, ya
no podremos ver desde el propio entorno la salida de consola de la ejecución del proyecto; u otro ejemplo, ya
no funcionará la compilación automática al cambiar archivos de código fuente. Si para nosotros estas son características importantes, lo mejor es continuar utilizando el
hosting out-of-process, al menos durante el desarrollo.
También es importante un efecto colateral algo inesperado, y que puede dar lugar a problemas. Dado que ahora el proceso se ejecuta dentro del
worker de IIS, el directorio por defecto retornado por
Directory.GetCurrentDirectory()
no será el de nuestra aplicación, sino el del propio IIS, como
C:\Windows\System32\inetsrv
o
C:\Program Files\IIS Express
.
Si nuestro código depende en algún punto de que el directorio de ejecución sea la carpeta de los binarios, fallará.
Afortunadamente tiene una solución sencilla, pues si queremos dejarlo todo en su sitio sólo debemos actualizar el directorio actual con una llamada manual a
Directory.SetCurrentDirectory()
cuando la aplicación arranque. Probablemente en muchos escenarios nos valga con establecerlo al
ContentRootPath
ofrecido por
IHostingEnvironment
:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
Directory.SetCurrentDirectory(env.ContentRootPath);
...
}
Asimismo, como se indica en
la documentación oficial del módulo ASP.NET Core, hay otra serie de cambios a tener en cuenta, entre otras:
- El servidor que se utilizará internamente ya no es Kestrel, sino IISHttpServer. Salvo que estemos usando características específicas de Kestrel, esto no debería afectarnos mucho.
- La arquitectura (x86 o x64) de la aplicación y el runtime deberá coincidir con el del pool de aplicaciones.
- El atributo
requestTimeout
de la configuración del módulo ya no será válido, aunque tiene sentido, pues este atributo definía el timeout de la petición interna entre IIS y Kestrel. - No será posible compartir app pools entre distintas aplicaciones.
- La parada de aplicaciones al hacer web deploy o usando app_offline.html puede retrasarse si hay conexiones abiertas.
En definitiva, el mensaje es que incluso
mejoras espectaculares y apetecibles como el hosting in-process pueden venir cargadas de efectos colaterales que debemos tener en cuenta. Antes de adoptarlas hay
ser prudentes y probarlas cuidadosamente en nuestros escenarios, para sólo dar el paso adelante cuando estemos seguros de que todo funciona con garantías.
Y para acabar con buen sabor de boca, añadiré un punto positivo: al usar el
hosting in-process podremos
detectar directamente las desconexiones de los clientes. Recordaréis que esto antes no era posible porque la desconexión física se producía en un servidor (IIS) distinto al que procesaba las peticiones (Kestrel), pero, al encontrarse ahora todo dentro del mismo proceso, podremos aprovechar esta característica para implementar lógica de
cancelación de peticiones. De esta forma, podremos informarnos mediante un
token de cancelación si el cliente desconectó, por ejemplo para evitar la continuación de un proceso cuya respuesta no va a ser recibida por nadie:
public Task<IActionResult> GenerateComplexReport(int id, CancellationToken cancellationToken)
{
// cancellationToken will be cancelled when the client disconnects
var report = await _reportGenerator.GenerateComplexReportAsync(id, cancellationToken);
if (report == null)
{
return Empty();
}
return Ok(report);
}
¿Cómo puedo volver al modelo anterior, el hosting out-of-process?
Si por cualquier motivo quisiéramos desactivar el
hosting in-process, bastaría como eliminar el elemento
<AspNetCoreHostingModel>
del archivo
.csproj
, o bien establecer su valor a
OutOfProcess
:
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>OutOfProcess</AspNetCoreHostingModel>
</PropertyGroup>
De esta forma, todo seguiría funcionando tal y como lo hacía antes de la versión 2.2 de ASP.NET Core.
¿Podemos usar el modo in-process sólo en producción?
Esto podría ser interesante si preferimos utilizar el modelo
out-of-process mientras desarrollamos, pero disfrutar del incremento brutal de rendimiento una vez pasemos a producción.
Aunque en un primer vistazo el modo de alojamiento parece depender directamente del valor que establezcamos en la propiedad
<AspNetCoreHostingModel>
del archivo del proyecto
.csproj
, en realidad este valor sólo se utiliza para generar apropiadamente el
web.config
que será utilizado por IIS para configurar el módulo ASP.NET Core al lanzar la aplicación. Por ejemplo, este es el
web.config
incluido al publicar un proyecto con el modo
in-process configurado en su
.csproj
:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<location path="." inheritInChildApplications="false">
<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\InProcessTest.dll" stdoutLogEnabled="false"
stdoutLogFile=".\logs\stdout" hostingModel="InProcess" />
</system.webServer>
</location>
</configuration>
Por tanto,
en tiempo de ejecución no tenemos forma de establecer uno u otro modelo de hosting porque cuando la aplicación arranque ya estará decidido, pero sí podemos hacerlo en tiempo de compilación o despliegue, que es cuando se genera el archivo
web.config
.
Para ello, podemos eliminar del archivo de proyecto
.csproj
el elemento
<AspNetCoreHostingModel>
y añadir el siguiente bloque, consiguiendo que el modelo de
hosting sea
in-process sólo cuando hayamos compilado con la configuración "Release" del proyecto, algo que normalmente ocurrirá cuando vayamos a desplegar a producción:
<PropertyGroup Condition="'$(Configuration)' == 'Release' ">
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
De esta forma, mientras estamos en modo "Debug" estaremos usando el
hosting out-of-process, mientras que el modelo
in-process sólo lo usaremos al ejecutar tras compilar o desplegar en modo "Release".
¿Cómo podemos saber en tiempo de ejecución si la aplicación se está ejecutando el modo in-process?
Pues si lo necesitáis para algo, la forma más sencilla, pero probablemente válida en muchos casos, sería consultar el nombre del proceso actualmente en ejecución con una llamada a
Process.GetCurrentProcess().ProcessName
. Cuando estamos utilizando el
hosting in-process, el nombre del proceso será "w3wp" o "iisexpress", mientras que en
out-of-process recibiremos "dotnet".
Pero también podemos hacerlo de forma algo más "pro" ;) Simplemente deberíamos determinar si el módulo ha sido cargado por el proceso actual utilizando la llamada
GetModuleHandle()
del
kernel del sistema operativo (obviamente, sólo funcionará en Windows):
public static class AspNetCoreModuleHelpers
{
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public static bool IsInProcess()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
&& GetModuleHandle("aspnetcorev2_inprocess.dll") != IntPtr.Zero;
}
}
Así, el siguiente código retornará el modo de alojamiento cuando se haga una petición a "/mode":
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseRouter(
route => route.MapGet("mode",
async ctx => await ctx.Response.WriteAsync(
"In-Process: " + AspNetCoreModuleHelpers.IsInProcess()
)
)
);
}
}
Espero que os haya resultado interesante, y que os ayude a sacar mayor provecho de esta interesante mejora de ASP.NET Core 2.2, o al menos hacerlo con mayor conocimiento de causa :)
Publicado en
Variable not found.