Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 26 de septiembre de 2017
ASP.NET CoreEn las versiones clásicas de ASP.NET, el archivo Global.asax proporcionaba vías para implementar lógica personalizada cuando la aplicación arrancaba y era detenida, lo que podía resultar bastante útil, por ejemplo, para registrar estos eventos en un log, precargar cachés, inicializar bases de datos, “engancharnos” a servicios externos, etc.

Por ejemplo, en el siguiente código vemos cómo podíamos aprovechar los eventos Application_Start() y Application_End() para guardar un registro básico de estos sucesos:
public class MvcApplication : System.Web.HttpApplication
{
    private static string _logFile;
    protected void Application_Start()
    {
        ...
        _logFile = Server.MapPath("log.txt");
        File.AppendAllText(_logFile, DateTime.Now + ": Starting\n");
    }

    protected void Application_End()
    {
        File.AppendAllText(_logFile, DateTime.Now + ": Stopping\n");
    }
}
Sabemos que en ASP.NET Core no existe Global.asax, por lo esta fórmula ya no está disponible. Sin embargo, el nuevo framework ofrece una alternativa bastante razonable mediante el interfaz IApplicationLifetime, proporcionando, entre otras cosas, vías para suscribirnos a eventos relacionados con el ciclo de vida de una aplicación.

Introducing IApplicationLifetime

El servicio IApplicationLifetime es registrado en el inyector de dependencias por el host de ASP.NET Core durante el proceso de configuración inicial, incluso antes de que el flujo de ejecución llegue a la clase Startup. Por tanto, podemos tener acceso a él en cualquier punto de la aplicación simplemente solicitándolo al inyector, como hacemos en el siguiente método Configure():
public class Startup
{
    ...
    public void Configure(IApplicationBuilder app, IApplicationLifetime applicationLifetime) 
    {
        ... 
    }
}
Este interfaz está definido de la siguiente forma:
public interface IApplicationLifetime
{
  /// <summary>
  /// Triggered when the application host has fully started and is about to wait
  /// for a graceful shutdown.
  /// </summary>
  CancellationToken ApplicationStarted { get; }

  /// <summary>
  /// Triggered when the application host is performing a graceful shutdown.
  /// Requests may still be in flight. Shutdown will block until this event completes.
  /// </summary>
  CancellationToken ApplicationStopping { get; }

  /// <summary>
  /// Triggered when the application host is performing a graceful shutdown.
  /// All requests should be complete at this point. Shutdown will block
  /// until this event completes.
  /// </summary>
  CancellationToken ApplicationStopped { get; }

  /// <summary>Requests termination the current application.</summary>
  void StopApplication();
}
Los tres primeros miembros, ApplicationStarted, ApplicationStopping y ApplicationStopped son los cancellation tokens usados por el framework para indicar distintos estados del ciclo de vida de la aplicación. Cada uno de ellos es cambiado al estado de cancelación cuando se completa la fase correspondiente; por ejemplo, el token ApplicationStarted es cancelado cuando la aplicación ha sido iniciada con éxito, o ApplicationStopped cuando ésta ha sido detenida.

Por tanto, podemos aprovechar estos miembros de IApplicationLifetime para introducir código personalizado. Basta con registrar delegados mediante el método Register() para que éstos sean invocados cuando se cancelen los tokens, consiguiendo así emular el comportamiento de los vetustos Application_Start() y Application_End() de ASP.NET clásico.

El siguiente código muestra cómo aplicar esto para conseguir el mismo log simple que vimos al principio del post:
public class Startup
{
    ...
    public void Configure(IApplicationBuilder app, IHostingEnvironment env,
                          IApplicationLifetime applicationLifetime)
    {
        var logPath = Path.Combine(env.ContentRootPath, "log.txt");       
        applicationLifetime.ApplicationStarted.Register(async () =>
        {
            await File.AppendAllTextAsync(logPath, DateTime.Now + ": Starting\n");
        });
        applicationLifetime.ApplicationStopped.Register(async () =>
        {
            await File.AppendAllTextAsync(logPath, DateTime.Now + ": Stopping\n");
        });

        ...
    }     
}    

Punto extra: ¿Qué es el método StopApplication()?

Como curiosidad, creo que vale la pena comentar también el miembro StopApplication() que encontramos en IApplicationLifetime. Como seguro podéis intuir, la invocación de este método detiene la aplicación sin dar más explicaciones… la verdad es que no se me ocurren muchos escenarios donde pueda resultar útil, pero en cualquier caso es interesante saber que está ahí ;)

Así, imaginemos el siguiente código en un controlador MVC:
public class AppController : Controller
{
    private readonly IApplicationLifetime _applicationLifetime;

    public AppController(IApplicationLifetime applicationLifetime)
    {
        _applicationLifetime = applicationLifetime;
    }

    public IActionResult Stop()
    {
        _applicationLifetime.StopApplication();
        return Content("Shutting down...");
    }
}
Si lo introducimos en una aplicación y lanzamos una petición hacia /app/stop, ésta simplemente se detendrá, mientras se envía como respuesta el texto “Shutting down…”. Visto desde la consola, una llamada a /app/stop provocaría la finalización de la aplicación (he simplificado un poco las trazas para que se vea mejor):
C:\test>dotnet run
Hosting environment: Production
Content root path: C:\test
Now listening on: http://localhost:54023
Application started. Press Ctrl+C to shut down.

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:54023/
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 2476.5013ms 200 text/html; charset=utf-8

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:54023/app/stop
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 4.6343ms 200 text/plain; charset=utf-8

Application is shutting down...

C:\test>
Y una curiosidad más: si ejecutáis el mismo código usando IIS como proxy inverso de Kestrel, veréis que la llamada a StopApplication() detiene la aplicación, pero ésta será iniciada de nuevo si hacéis inmediatamente después otra llamada al sitio web. Esto es así porque, cuando IIS recibe la petición, el módulo ASP.NET Core detecta que la aplicación se ha detenido y vuelve a lanzarla, todo de forma automática :)

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!