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, 15 de marzo de 2022
ASP.NET Core

En los tiempos de ASP.NET "clásico", cuando los settings de la aplicación los almacenábamos en el viejo Web.config, cualquier intento de cambio de valores mientras ejecutábamos la aplicación era inevitablemente sinónimo de reinicio. Esto, aunque bastante molesto, tenía sentido porque el mismo archivo XML se utilizaba para almacenar tanto los valores de configuración "de negocio" como aspectos puramente técnicos o del comportamiento de la infraestructura de ASP.NET, y la aplicación debía reiniciarse para poder aplicarlos.

Con la llegada del sistema de settings de .NET Core esto mejoró bastante, introduciendo la posibilidad de almacenar valores en bastantes orígenes distintos (archivos .json, .ini, .xml, variables de entorno, parámetros de línea de comandos, user secrets, diccionarios en memoria y muchos otros, incluso totalmente personalizados), y nuevas fórmulas para la obtención de éstos, como la inyección de dependencias, settings tipados y, por fin, la posibilidad de realizar cambios en caliente.

En aplicaciones .NET Core y posteriores a .NET 5, lo más habitual es que los settings de las aplicaciones estén almacenados el archivo appsettings.json (y los correspondientes appsettings.<EnvironmentName>.json), y, sólo en desarrollo, los user secrets. En estos casos, dado que se trata de settings respaldados por archivos físicos, son configurados por defecto de forma que si se detectan cambios en los archivos, los valores de los settings son refrescados, por lo que la aplicación obtendrá siempre los valores más recientes.

En realidad, esto no es tan rígido, y podemos configurar si por defecto queremos habilitar los cambios en caliente mediante la entrada de configuración hostBuilder:reloadConfigOnChange, cuyo valor por defecto es true.

Esto podemos probarlo con facilidad si creamos una aplicación ASP.NET Core 6 o superior vacía, e introducimos el siguiente contenido en Program.cs:

var app = WebApplication.CreateBuilder().Build();
app.MapGet("/", (IConfiguration config) => $"Hello {config["Name"]}!");
app.Run();

A continuación, si ejecutamos la aplicación y vamos haciendo cambios a una propiedad llamada "Name" definida en el appsettings.json, veremos que cada vez que refrescamos la página aparece el valor actualizado.

{
  "Name":  "John",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Hay otros orígenes donde esto no tiene sentido. Por ejemplo, durante la inicialización de la aplicación también se configuran como orígenes de settings las variables de entorno del sistema y los parámetros de línea de comandos. En ambos casos, los settings serán cargados de forma automática exclusivamente durante la inicialización, y mantendrán sus valores durante toda la vida de la aplicación.

Modificar valores de settings desde código

Aparte del refresco automático, que será el que utilicemos en la mayoría de ocasiones, también es interesante saber que los valores de IConfiguration son modificables desde código. Es decir, podríamos hacer lo siguiente:

var app = WebApplication.CreateBuilder().Build();
app.MapGet("/", (IConfiguration config) => $"Hello {config["Name"]}!");

app.MapGet("/{name}", (IConfiguration config, string name) => {
    config["Name"] = name;
    return Results.Redirect("/");
});

app.Run();

De esta forma, si el valor de Name en appsettings.json fuera "John", al ejecutar la aplicación veríamos "Hello John!" en el navegador. Si inmediatamente después accedemos a "/Peter", el valor de Name se sobrescribirá y en el browser veremos el saludo "Hello Peter!".

Sin embargo, esto no significa que el nuevo valor sea persistente: el cambio lo hacemos sólo en memoria. Si la aplicación se reinicia, el setting volverá al valor especificado en los orígenes (archivo JSON, entorno, etc.)

Ojo también a los efectos secundarios. Aunque todo parece correcto, en realidad lo que hemos hecho antes provoca un problema: una vez hemos establecido desde código el valor de un setting, éste dejará de actualizarse de forma automática si, por ejemplo, volvemos a modificar su valor en appsettings.json. Es decir, siguiendo con el ejemplo anterior, una vez hemos establecido el valor de Name a "Peter", aunque modifiquemos el archivo .json, seguiremos viendo "Peter" de por vida.

¿Y por qué ocurre esto? Internamente, el framework almacena una lista de los proveedores de settings configurados, junto con los valores de cada uno de los parámetros obtenidos a través de él; esquemática y resumidamente, al iniciar la ejecución del ejemplo anterior podríamos tener una estructura en memoria como la siguiente:

  • Origen: diccionario en memoria (MemoryConfigurationProvider)
    • (Ningún valor establecido)
  • Origen: appsettings.json (JsonConfigurationProvider)
    • Name=John
  • (Otros orígenes que no importan en este momento...)

Cuando ejecutamos algo como config["Name"]="Peter", el valor de Name es modificado en todos los proveedores, por lo que la estructura pasaría a ser la siguiente:

  • Origen: diccionario en memoria (MemoryConfigurationProvider)
    • Name=Peter
  • Origen: appsettings.json (JsonConfigurationProvider)
    • Name=Peter
  • (Otros orígenes que no importan en este momento...)

Ahora imaginemos que actualizamos el appsettings.json, cambiando la propiedad Name a "Frank". Al detectar los cambios en el archivo, el proveedor JsonConfigurationProvider recargaría los valores, por lo que la nueva estructura sería la siguiente:

  • Origen: diccionario en memoria (MemoryConfigurationProvider)
    • Name=Peter
  • Origen: appsettings.json (JsonConfigurationProvider)
    • Name=Frank
  • (Otros orígenes que no importan en este momento...)

Como podéis ver, ahí viene el problema. Si a continuación intentamos obtener el valor actual de config["Name"], obtendremos "Peter", puesto que el valor almacenado en el proveedor de valores en memoria tiene prioridad sobre el del archivo JSON debido al orden en que son añadidos a la lista de proveedores.

Por último, dejad que os recuerde un par de cosas: estos cambios en los valores de configuración solo serán aplicados mientras el servicio siga activo, por lo que si es detenido todo volverá a su estado original (según se indique en los orígenes de settings persistentes). También, el hecho de que se almacenen en memoria podría crear problemas cuando la aplicación corre en varias instancias, pues cada una podría almacenar valores distintos en memoria.

Publicado en Variable not found.

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