Cambiar en caliente la configuración fuertemente tipada en ASP.NET Core 2.0 (II)
En una entrada anterior, vimos como cambiar la configuración personalizada y fuertemente tipada, en caliente. Te recomiendo leer primero esa entrada si no lo has hecho ya, ya que esta entrada se basa en aquella.
Todo esto lo hacíamos cuando la configuración la teníamos en el archivo appsettings.json.
Desde ASP.NET Core 1.1, se ha simplificado mucho todo esto, y ahora resulta más fácil trabajar con cambios de configuración en caliente.
Sin embargo, existe también la posibilidad de agregar nuestro propio JSON de configuración, e incluso tener más de uno.
Eso es justo lo que vamos a ver ahora, es decir, trabajar con appsettings.json y con mySettings.json, un archivo de configuración que nos hemos creado para hacer esta demostración y explicar un par de cosas que conviene tengas en consideración.
Los primero de todo, será crear un archivo JSON, mySettings.json, e introducir dentro este contenido:
{ "Custom": { "Country": "en-us" } }
Lógicamente, tendremos que crear nuestra clase de configuración, para cargar estos datos.
Esta clase, CustomSettings, tendrá este aspecto:
public class CustomSettings { public string Country { get; set; } }
Ahora bien, lo que tenemos que hacer dentro de la clase Startup, es cargar la configuración de mySettings.json.
Para ello, voy a cambiar el constructor de la clase Startup que viene por defecto.
En mi caso, esta clase quedará de la siguiente forma:
public class Startup { public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json") .AddJsonFile("mySettings.json") .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.Configure<ContactSettings>(Configuration.GetSection("Contact")); services.Configure<CustomSettings>(Configuration.GetSection("Custom")); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }
Una vez hecho esto, modificaremos la vista Contact.cshtml para aplicar los cambios correspondientes.
Nuestra vista quedará de la siguiente forma:
@{ ViewData["Title"] = "Contact"; } <h2>@ViewData["Title"].</h2> <h3>@ViewData["Message"]</h3> <address> @Html.Raw(@ViewData["ContactAddress"]) </address> <address> <strong>@ViewData["ContactSupportName"]</strong> <a href="mailto:@ViewData["ContactSupportMail"]">@ViewData["ContactSupportMail"]</a><br /> <strong>@ViewData["ContactMarketingName"]</strong> <a href="mailto:@ViewData["ContactMarketingMail"]">@ViewData["ContactMarketingMail"]</a> </address> <p>@ViewData["CustomCountry"]</p>
Sin embargo, nos queda aún indicar en nuestro controlador HomeController, lo que queremos hacer, que no es otra cosa que leer la configuración, y estar al tanto de si esta cambia, para aplicar los cambios correspondientes en la vista o donde tengamos pensado aplicarlos.
Nuestro controlador quedará de la siguiente forma:
public class HomeController : Controller { private readonly ContactSettings _contactSettings; private readonly CustomSettings _customSettings; public HomeController(IOptionsSnapshot<ContactSettings> contactSettings, IOptionsSnapshot<CustomSettings> customSettings) { _contactSettings = contactSettings.Value; _customSettings = customSettings.Value; } public IActionResult Index() { return View(); } public IActionResult About() { ViewData["Message"] = "Your application description page."; return View(); } public IActionResult Contact() { ViewData["Message"] = "Your contact page."; ViewData["ContactAddress"] = _contactSettings.Address; ViewData["ContactSupportName"] = _contactSettings.SupportDetails.Name; ViewData["ContactSupportMail"] = _contactSettings.SupportDetails.Mail; ViewData["ContactMarketingName"] = _contactSettings.MarketingDetails.Name; ViewData["ContactMarketingMail"] = _contactSettings.MarketingDetails.Mail; if (_customSettings.Country != null) { var cultureInfo = new CultureInfo(_customSettings.Country); ViewData["CustomCountry"] = DateTime.Now.ToString("d", cultureInfo); } return View(); } public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } }
Una vez aplicados los cambios, si ejecutamos nuestra aplicación veremos algo parecido a esto:
Pero sorprendentemente con respecto a los otros ejemplos de configuración que habíamos visto antes, si cambiamos algo en cualquiera de los dos archivos de configuración, el controlador no se habrá enterado de estos cambios y seguiremos teniendo en memoria los datos de configuración anteriores al cambio.
Y eso pese a indicar en el constructor del controlador la interfaz IOptionsShapshot.
¿Porqué?.
Sencillamente porque al indicar con AddJsonFile (soportado por ASP.NET Core 1.1 ó superior) el archivo JSON que debemos utilizar, le debemos indicar también, si queremos recargar la configuración en caso de que el archivo de configuración sea modificado.
Esto se realiza con la propiedad reloadOnChange a true.
Por lo tanto, para aplicar los cambios cuando uno o ambos archivos JSON sean modificados, deberemos indicar este código:
public class Startup { public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile("mySettings.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.Configure<ContactSettings>(Configuration.GetSection("Contact")); services.Configure<CustomSettings>(Configuration.GetSection("Custom")); services.AddMvc(); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }
Ahora sí, si ejecutamos nuestra aplicación nuevamente, veremos en caliente los cambios aplicados en los archivos de configuración.
Por último, indicar también que los archivos de configuración son leídos en el orden que se indica.
En nuestro caso, leemos primero appsettings.json, luego mySettings.json, y finalmente las variables de entorno.
Dependiendo de nuestras necesidades, podemos variar el orden.
También podemos cargar archivos de configuración según el entorno. Es algo que no hemos mencionado ni visto en este ejemplo. El entorno es normalmente uno de los siguientes:
- Development
- Staging
- Production
Podrás acceder a más información sobre aplicar archivos de configuración según el entorno en este enlace.
¡Happy Coding!