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, 12 de julio de 2016
ASP.NET CoreCuando ASP.NET “clásico” modificábamos algún setting de la aplicación almacenado en el archivo web.config, esto traía como consecuencia directa el reciclado del pool, o en otras palabras, la aplicación se reiniciaba irremediablemente para recargar los nuevos valores de configuración.

Pero como sabemos, en ASP.NET Core el sistema de configuración ha cambiado para mejor, y ahora los settings podemos almacenarlos en archivos JSON independientes del resto de configuraciones del proyecto, así como en otros formatos y ubicaciones. Y ya que se ponían con el tema, el equipo de ASP.NET ha aprovechado para hacer que podamos modificar los settings sobre los archivos de configuración y que éstos sean aplicados sobre la marcha, sin necesidad  de volver a arrancar la aplicación.


¿Y cómo conseguimos esto? Pues la verdad es que lo han puesto bastante sencillo, como podemos ver en la siguiente porción de código de la clase de inicialización de la aplicación:
public class Startup
{
   public Startup(IHostingEnvironment env)
   {
    var builder = new ConfigurationBuilder()
       .SetBasePath(env.ContentRootPath)
       .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
       .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

    // More startup code…
   }
}
¡Así de fácil! Estableciendo a cierto el valor del parámetro reloadOnChange, cuando leamos los settings de nuestra aplicación mediante una instancia de IConfiguration los valores obtenidos siempre estarán frescos como lechugas :)

Por ejemplo, imaginemos el siguiente contenido en el archivo de settings:
{
    "Title": "Probando, probando…"
}
Si accedemos a los parámetros de configuración como se muestra en el siguiente código, podremos observar que las peticiones a "/settings/title" retornan el valor especificado en el archivo de configuración, y aunque lo modifiquemos durante la ejecución, al realizar de nuevo la petición aparecerá actualizado sin necesidad de echar abajo la aplicación:
public class SettingsController : Controller
{
    private readonly IConfiguration _configuration;

    public SettingsController(IConfiguration configuration)
    {
        _configuration = configuration;
    }
    public IActionResult Title()
    {
        return Content(_configuration["Title"]);
    }
}
Pero ojo, esto ocurre sólo cuando accedemos a los datos de forma no tipada, mediante la instancia de IConfiguration. En cambio, si utilizamos utilizamos la alternativa tipada basada en IOptions<T>, los datos no serán actualizados porque la instancia de nuestra clase de settings es cargada exclusivamente durante el arranque. Pero no os preocupéis porque seguro que podemos idear alguna forma sencilla de conseguirlo ;)

Un posible enfoque es registrar en el contenedor de una instancia singleton de la clase que represente nuestros settings, la misma que utilizamos con IOptions<T>, y asegurarnos de que sus valores cambian cuando el archivo de settings es modificado. De esta forma, siempre que un componente necesite acceder a los settings, tendrá la versión más actual de los valores.

El siguiente código simplificado de la clase de inicialización muestra cómo podríamos conseguirlo:
public class Startup
{
    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
            // Add other settings providers

        Configuration = builder.Build();

        // (1) Creamos una instancia singleton de la clase de settings

        MySettings = new MySettings() { Title = "Default title"};

        // (2) Inicializar la instancia con el archivo de configuración
        // Ojo, requiere el paquete Microsoft.Extension.Configuration.Binders

        Configuration.Bind(MySettings);

        // (3) Aseguramos que se recarga cuando cambia el archivo

        var token = Configuration.GetReloadToken();
        token.RegisterChangeCallback(c =>
        {
            // Actualizar la instancia tras cada cambio
            Configuration.Bind(MySettings);
        }, this);
    }

    // Instancia singleton para settings tipados
    public MySettings MySettings { get; }
        
    // Instancia singleton para settings sin tipo
    public IConfigurationRoot Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // (4) Registramos la instancia como singleton en el DI
        services.AddSingleton<MySettings>(MySettings);

        // Configure other services here...
    }

    public void Configure(IApplicationBuilder app, 
                          IHostingEnvironment env, 
                          ILoggerFactory loggerFactory)
    {
        // Configure the pipeline here
    }
}

public class MySettings
{
    public string Title {get; set; }
}
A grandes rasgos, lo que hacemos es:
  1. Creamos una instancia singleton de la clase que proveerá acceso tipado a los settings.
  2. La inicializamos desde el objeto de configuración mediante su extensor Bind(), disponible en el paquete Microsoft.Extensions.Configuration.Binders.
  3. Obtenemos un token de recarga de la configuración y lo usamos para definir una función callback que será invocada cuando el sistema detecte cambios en ella. En dicha función actualizamos la instancia singleton de la clase tipada de settings.
  4. Registramos la instancia en el contenedor de inyección de dependencias.
De esa forma, ya podremos acceder a la configuración fresca solicitando al sistema de inyección de dependencias nuestra instancia de settings tipados, por ejemplo:
public class SettingController : Controller
{
    private readonly MySettings _settings;

    public SettingController(MySettings settings)
    {
        _settings = settings;
    }

    public IActionResult Title()
    {
        return Content(_settings.Title);
    }
}
Espero que os resulte de utilidad :)

Publicado en Variable not found.

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