From 1b2d0b68a83fdbb692bad84f85c78d8747dc1655 Mon Sep 17 00:00:00 2001 From: Keith Kjer Date: Wed, 5 Feb 2020 15:16:07 -0800 Subject: [PATCH 1/6] Add environment var to allow disabling live reload in default builder --- src/Hosting/Hosting/src/Host.cs | 8 +++-- src/Hosting/Hosting/test/HostTests.cs | 47 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Hosting/Hosting/src/Host.cs b/src/Hosting/Hosting/src/Host.cs index be991885953..3756582ce08 100644 --- a/src/Hosting/Hosting/src/Host.cs +++ b/src/Hosting/Hosting/src/Host.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; @@ -71,8 +72,11 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) { var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); + var reloadOnChange = Convert.ToBoolean( + Environment.GetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD") ?? "true"); + + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName)) { diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index 6521988b7a2..e2ea9f38cd0 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Tracing; using System.IO; using System.Linq; +using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -81,6 +82,52 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild() Assert.Throws(() => hostBuilder.Build()); } + [Fact] + public void CreateDefaultBuilder_ConfigJsonDoesNotReload() + { + Environment.SetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD", "false"); + Environment.CurrentDirectory = Path.GetTempPath(); + Func saveRandomConfig = () => + { + var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; + File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); + return newMessage; + }; + var dynamicConfigMessage1 = saveRandomConfig(); + var host = Host.CreateDefaultBuilder().Build(); + var config = host.Services.GetRequiredService(); + + Assert.Equal(dynamicConfigMessage1, config["Hello"]); + + var dynamicConfigMessage2 = saveRandomConfig(); + Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. + Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. + Assert.Equal(dynamicConfigMessage1, config["Hello"]); // Config did not reload + } + + [Fact] + public void CreateDefaultBuilder_ConfigJsonDoesReload() + { + Environment.SetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD", "true"); + Environment.CurrentDirectory = Path.GetTempPath(); + Func saveRandomConfig = () => + { + var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; + File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); + return newMessage; + }; + var dynamicConfigMessage1 = saveRandomConfig(); + var host = Host.CreateDefaultBuilder().Build(); + var config = host.Services.GetRequiredService(); + + Assert.Equal(dynamicConfigMessage1, config["Hello"]); + + var dynamicConfigMessage2 = saveRandomConfig(); + Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. + Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. + Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload. + } + internal class ServiceA { } internal class ServiceB From 126381c5526d59eb573386ffc642820091555c32 Mon Sep 17 00:00:00 2001 From: Keith Kjer Date: Fri, 7 Feb 2020 12:47:26 -0800 Subject: [PATCH 2/6] Switch to using hostingContext, rename flag to fit nomenclature. --- src/Hosting/Hosting/src/Host.cs | 6 ++---- src/Hosting/Hosting/test/HostTests.cs | 24 ++++++++++++++---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/Hosting/Hosting/src/Host.cs b/src/Hosting/Hosting/src/Host.cs index 3756582ce08..e3d9707c7b0 100644 --- a/src/Hosting/Hosting/src/Host.cs +++ b/src/Hosting/Hosting/src/Host.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; @@ -71,9 +70,8 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) builder.ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; - - var reloadOnChange = Convert.ToBoolean( - Environment.GetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD") ?? "true"); + + var reloadOnChange = hostingContext.Configuration.GetValue("HOSTBUILDER_CONFIG_RELOAD", true); config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index e2ea9f38cd0..302dc3f5848 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -85,21 +85,23 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild() [Fact] public void CreateDefaultBuilder_ConfigJsonDoesNotReload() { - Environment.SetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD", "false"); + Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER_CONFIG_RELOAD", "false"); Environment.CurrentDirectory = Path.GetTempPath(); - Func saveRandomConfig = () => + + string SaveRandomConfig() { var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); return newMessage; - }; - var dynamicConfigMessage1 = saveRandomConfig(); + } + + var dynamicConfigMessage1 = SaveRandomConfig(); var host = Host.CreateDefaultBuilder().Build(); var config = host.Services.GetRequiredService(); Assert.Equal(dynamicConfigMessage1, config["Hello"]); - var dynamicConfigMessage2 = saveRandomConfig(); + var dynamicConfigMessage2 = SaveRandomConfig(); Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. Assert.Equal(dynamicConfigMessage1, config["Hello"]); // Config did not reload @@ -108,21 +110,23 @@ public void CreateDefaultBuilder_ConfigJsonDoesNotReload() [Fact] public void CreateDefaultBuilder_ConfigJsonDoesReload() { - Environment.SetEnvironmentVariable("ASPNETCORE_BUILDER_CONFIG_RELOAD", "true"); + Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER_CONFIG_RELOAD", "true"); Environment.CurrentDirectory = Path.GetTempPath(); - Func saveRandomConfig = () => + + string SaveRandomConfig() { var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); return newMessage; - }; - var dynamicConfigMessage1 = saveRandomConfig(); + } + + var dynamicConfigMessage1 = SaveRandomConfig(); var host = Host.CreateDefaultBuilder().Build(); var config = host.Services.GetRequiredService(); Assert.Equal(dynamicConfigMessage1, config["Hello"]); - var dynamicConfigMessage2 = saveRandomConfig(); + var dynamicConfigMessage2 = SaveRandomConfig(); Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload. From 1a9b6155e9dc45112ced0cfa619ed01999b3a93a Mon Sep 17 00:00:00 2001 From: Keith Kjer Date: Mon, 10 Feb 2020 16:54:04 -0800 Subject: [PATCH 3/6] Change flag to memory data source. Change content root default instead of runtime default. --- src/Hosting/Hosting/test/HostTests.cs | 34 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index 302dc3f5848..ff2f26846dc 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -85,18 +85,26 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild() [Fact] public void CreateDefaultBuilder_ConfigJsonDoesNotReload() { - Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER_CONFIG_RELOAD", "false"); - Environment.CurrentDirectory = Path.GetTempPath(); + var reloadFlagConfig = new Dictionary() {{ "HOSTBUILDER_CONFIG_RELOAD", "false" }}; + var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig() { var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; - File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); + File.WriteAllText(appSettingsPath, $"{{ \"Hello\": \"{newMessage}\" }}"); return newMessage; } var dynamicConfigMessage1 = SaveRandomConfig(); - var host = Host.CreateDefaultBuilder().Build(); + + var host = Host.CreateDefaultBuilder() + .UseContentRoot(Path.GetDirectoryName(appSettingsPath)) + .ConfigureHostConfiguration(builder => + { + builder.AddInMemoryCollection(reloadFlagConfig); + }) + .Build(); + var config = host.Services.GetRequiredService(); Assert.Equal(dynamicConfigMessage1, config["Hello"]); @@ -110,18 +118,26 @@ string SaveRandomConfig() [Fact] public void CreateDefaultBuilder_ConfigJsonDoesReload() { - Environment.SetEnvironmentVariable("DOTNET_HOSTBUILDER_CONFIG_RELOAD", "true"); - Environment.CurrentDirectory = Path.GetTempPath(); + var reloadFlagConfig = new Dictionary() { { "HOSTBUILDER_CONFIG_RELOAD", "true" } }; + var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig() { var newMessage = $"Hello ASP.NET Core: {Guid.NewGuid():N}"; - File.WriteAllText(Path.Combine(Path.GetTempPath(), "appsettings.json"), $"{{ \"Hello\": \"{newMessage}\" }}"); + File.WriteAllText(appSettingsPath, $"{{ \"Hello\": \"{newMessage}\" }}"); return newMessage; } var dynamicConfigMessage1 = SaveRandomConfig(); - var host = Host.CreateDefaultBuilder().Build(); + + var host = Host.CreateDefaultBuilder() + .UseContentRoot(Path.GetDirectoryName(appSettingsPath)) + .ConfigureHostConfiguration(builder => + { + builder.AddInMemoryCollection(reloadFlagConfig); + }) + .Build(); + var config = host.Services.GetRequiredService(); Assert.Equal(dynamicConfigMessage1, config["Hello"]); @@ -129,7 +145,7 @@ string SaveRandomConfig() var dynamicConfigMessage2 = SaveRandomConfig(); Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. - Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload. + Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload from disk } internal class ServiceA { } From de16b88781a0a8171a6268a43f8b1211679bdc8b Mon Sep 17 00:00:00 2001 From: Keith Kjer Date: Tue, 3 Mar 2020 15:59:46 -0800 Subject: [PATCH 4/6] Update config key to be hierarchical. Change await on positive case to be longer and cancel using the reload token. --- src/Hosting/Hosting/src/Host.cs | 2 +- src/Hosting/Hosting/test/HostTests.cs | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Hosting/Hosting/src/Host.cs b/src/Hosting/Hosting/src/Host.cs index e3d9707c7b0..023749a37c1 100644 --- a/src/Hosting/Hosting/src/Host.cs +++ b/src/Hosting/Hosting/src/Host.cs @@ -71,7 +71,7 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) { var env = hostingContext.HostingEnvironment; - var reloadOnChange = hostingContext.Configuration.GetValue("HOSTBUILDER_CONFIG_RELOAD", true); + var reloadOnChange = hostingContext.Configuration.GetValue("hostbuilder:configreload", true); config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index ff2f26846dc..644b2d3cd53 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -7,6 +7,7 @@ using System.Diagnostics.Tracing; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -83,9 +84,9 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild() } [Fact] - public void CreateDefaultBuilder_ConfigJsonDoesNotReload() + public async Task CreateDefaultBuilder_ConfigJsonDoesNotReload() { - var reloadFlagConfig = new Dictionary() {{ "HOSTBUILDER_CONFIG_RELOAD", "false" }}; + var reloadFlagConfig = new Dictionary() {{ "hostbuilder:configreload", "false" }}; var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig() @@ -110,15 +111,15 @@ string SaveRandomConfig() Assert.Equal(dynamicConfigMessage1, config["Hello"]); var dynamicConfigMessage2 = SaveRandomConfig(); - Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. + await Task.Delay(1000); // Give reload time to fire if it's going to. Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. Assert.Equal(dynamicConfigMessage1, config["Hello"]); // Config did not reload } [Fact] - public void CreateDefaultBuilder_ConfigJsonDoesReload() + public async Task CreateDefaultBuilder_ConfigJsonDoesReload() { - var reloadFlagConfig = new Dictionary() { { "HOSTBUILDER_CONFIG_RELOAD", "true" } }; + var reloadFlagConfig = new Dictionary() { { "hostbuilder:configreload", "true" } }; var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig() @@ -143,7 +144,16 @@ string SaveRandomConfig() Assert.Equal(dynamicConfigMessage1, config["Hello"]); var dynamicConfigMessage2 = SaveRandomConfig(); - Task.Delay(1000).Wait(); // Give reload time to fire if it's going to. + + var configReloadedCancelTokenSource = new CancellationTokenSource(); + var configReloadedCancelToken = configReloadedCancelTokenSource.Token; + + config.GetReloadToken().RegisterChangeCallback(o => + { + //configReloadedCancelTokenSource.Cancel(); + }, null); + // Wait for up to 10 seconds, if config reloads at any time, cancel the wait. + await Task.WhenAny(Task.Delay(10000, configReloadedCancelToken)); // Task.WhenAny ignores the task throwing on cancellation. Assert.NotEqual(dynamicConfigMessage1, dynamicConfigMessage2); // Messages are different. Assert.Equal(dynamicConfigMessage2, config["Hello"]); // Config DID reload from disk } From 51c3a03f7626cf0c57310f83a2279185b171745b Mon Sep 17 00:00:00 2001 From: Keith Kjer Date: Tue, 3 Mar 2020 16:05:36 -0800 Subject: [PATCH 5/6] Uncomment a development test case. --- src/Hosting/Hosting/test/HostTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index 644b2d3cd53..f09d5f050a7 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -150,7 +150,7 @@ string SaveRandomConfig() config.GetReloadToken().RegisterChangeCallback(o => { - //configReloadedCancelTokenSource.Cancel(); + configReloadedCancelTokenSource.Cancel(); }, null); // Wait for up to 10 seconds, if config reloads at any time, cancel the wait. await Task.WhenAny(Task.Delay(10000, configReloadedCancelToken)); // Task.WhenAny ignores the task throwing on cancellation. From f0d48a3cae6fabe0712bf0601e1b7b312d3d3b5e Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Thu, 19 Mar 2020 09:28:08 -0700 Subject: [PATCH 6/6] Apply suggestions from code review --- src/Hosting/Hosting/src/Host.cs | 2 +- src/Hosting/Hosting/test/HostTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Hosting/Hosting/src/Host.cs b/src/Hosting/Hosting/src/Host.cs index 023749a37c1..a3763aafa92 100644 --- a/src/Hosting/Hosting/src/Host.cs +++ b/src/Hosting/Hosting/src/Host.cs @@ -71,7 +71,7 @@ public static IHostBuilder CreateDefaultBuilder(string[] args) { var env = hostingContext.HostingEnvironment; - var reloadOnChange = hostingContext.Configuration.GetValue("hostbuilder:configreload", true); + var reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true); config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); diff --git a/src/Hosting/Hosting/test/HostTests.cs b/src/Hosting/Hosting/test/HostTests.cs index f09d5f050a7..a2f9c81a5a0 100644 --- a/src/Hosting/Hosting/test/HostTests.cs +++ b/src/Hosting/Hosting/test/HostTests.cs @@ -86,7 +86,7 @@ public void CreateDefaultBuilder_EnablesValidateOnBuild() [Fact] public async Task CreateDefaultBuilder_ConfigJsonDoesNotReload() { - var reloadFlagConfig = new Dictionary() {{ "hostbuilder:configreload", "false" }}; + var reloadFlagConfig = new Dictionary() {{ "hostbuilder:reloadConfigOnChange", "false" }}; var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig() @@ -119,7 +119,7 @@ string SaveRandomConfig() [Fact] public async Task CreateDefaultBuilder_ConfigJsonDoesReload() { - var reloadFlagConfig = new Dictionary() { { "hostbuilder:configreload", "true" } }; + var reloadFlagConfig = new Dictionary() { { "hostbuilder:reloadConfigOnChange", "true" } }; var appSettingsPath = Path.Combine(Path.GetTempPath(), "appsettings.json"); string SaveRandomConfig()