From 2186286541315c625918bf7f252bc22a3a0d5506 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 13 May 2021 20:37:55 -0700 Subject: [PATCH 01/10] Don't double-ConfigureHostConfiguration --- src/DefaultBuilder/src/ConfigureHostBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DefaultBuilder/src/ConfigureHostBuilder.cs b/src/DefaultBuilder/src/ConfigureHostBuilder.cs index 73c9b7e03598..4f45e9a837a4 100644 --- a/src/DefaultBuilder/src/ConfigureHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureHostBuilder.cs @@ -63,7 +63,6 @@ public IHostBuilder ConfigureHostConfiguration(Action con _environment.ApplyConfigurationSettings(_hostConfiguration.Build()); Configuration.ChangeFileProvider(_environment.ContentRootFileProvider); - _operations += b => b.ConfigureHostConfiguration(configureDelegate); return this; } From fb9f5487d58d3661b3884fa80a257a2d096d9e2f Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 14 May 2021 12:11:01 -0700 Subject: [PATCH 02/10] cleanup comments --- src/DefaultBuilder/src/Configuration.cs | 1 - src/DefaultBuilder/src/WebApplicationBuilder.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index 5c3abd260aeb..2b41fb042ac1 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -49,7 +49,6 @@ public IConfigurationSection GetSection(string key) IDictionary IConfigurationBuilder.Properties => _builder.Properties; - // TODO: Handle modifications to Sources and keep the configuration root in sync IList IConfigurationBuilder.Sources => Sources; internal IList Sources { get; } diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 3be41cf94061..8eced6f7c2f9 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -61,13 +61,13 @@ internal WebApplicationBuilder(Assembly? callingAssembly, string[]? args = null) public Configuration Configuration { get; } = new(); /// - /// A collection of logging providers for the applicaiton to compose. This is useful for adding new logging providers. + /// A collection of logging providers for the application to compose. This is useful for adding new logging providers. /// public ILoggingBuilder Logging { get; } /// /// An for configuring server specific properties, but not building. - /// To build after configuruation, call . + /// To build after configuration, call . /// public ConfigureWebHostBuilder WebHost { get; } From 33d75dd0d3a987fae13bae0f4655aabe76baba52 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 14 May 2021 12:46:07 -0700 Subject: [PATCH 03/10] Clear duplicate sources --- src/DefaultBuilder/src/WebApplicationBuilder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 8eced6f7c2f9..e82aa0eba1fa 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -167,6 +167,10 @@ private void ConfigureWebHost(IWebHostBuilder genericWebHostBuilder) _hostBuilder.ConfigureAppConfiguration((hostContext, builder) => { + // All the sources in builder.Sources should be in Configuration.Sources + // already thanks to the BootstrapHostBuilder. + builder.Sources.Clear(); + foreach (var s in Configuration.Sources) { builder.Sources.Add(s); From 917ec4b8a5419f06b60aaca5fdb525b18d6082be Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 14 May 2021 14:15:38 -0700 Subject: [PATCH 04/10] WIP Improve Configuration https://github.com/dotnet/runtime/issues/51770#issuecomment-828915774 --- .../src/BootstrapHostBuilder.cs | 4 +- src/DefaultBuilder/src/Configuration.cs | 433 +++++++++++++++--- .../src/ConfigureHostBuilder.cs | 2 +- .../src/ConfigureWebHostBuilder.cs | 2 +- .../src/PublicAPI.Unshipped.txt | 1 + 5 files changed, 363 insertions(+), 79 deletions(-) diff --git a/src/DefaultBuilder/src/BootstrapHostBuilder.cs b/src/DefaultBuilder/src/BootstrapHostBuilder.cs index 563dba7907a4..7b866620abf8 100644 --- a/src/DefaultBuilder/src/BootstrapHostBuilder.cs +++ b/src/DefaultBuilder/src/BootstrapHostBuilder.cs @@ -39,7 +39,7 @@ public IHostBuilder ConfigureAppConfiguration(Action con { configureDelegate(_configuration); _environment.ApplyConfigurationSettings(_configuration); - _configuration.ChangeBasePath(_environment.ContentRootPath); + //_configuration.ChangeBasePath(_environment.ContentRootPath); return this; } diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index 2b41fb042ac1..3f13df68d3a2 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -4,125 +4,335 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; -// TODO: Microsft.Extensions.Configuration API Proposal namespace Microsoft.AspNetCore.Builder { + ///// + ///// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot. + ///// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. + ///// + //public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder + //{ + // private readonly ConfigurationBuilder _builder = new(); + // private IConfigurationRoot _configuration; + + // /// + // /// Gets or sets a configuration value. + // /// + // /// The configuration key. + // /// The configuration value. + // public string this[string key] { get => _configuration[key]; set => _configuration[key] = value; } + + // /// + // /// Gets a configuration sub-section with the specified key. + // /// + // /// The key of the configuration section. + // /// The . + // /// + // /// This method will never return null. If no matching sub-section is found with the specified key, + // /// an empty will be returned. + // /// + // public IConfigurationSection GetSection(string key) + // { + // return _configuration.GetSection(key); + // } + + // /// + // /// Gets the immediate descendant configuration sub-sections. + // /// + // /// The configuration sub-sections. + // public IEnumerable GetChildren() => _configuration.GetChildren(); + + // IDictionary IConfigurationBuilder.Properties => _builder.Properties; + + // IList IConfigurationBuilder.Sources => Sources; + + // internal IList Sources { get; } + + // IEnumerable IConfigurationRoot.Providers => _configuration.Providers; + + // /// + // /// Creates a new . + // /// + // public Configuration() + // { + // _configuration = _builder.Build(); + // Sources = new ConfigurationSources(_builder.Sources, this); + // } + + // internal void ChangeBasePath(string path) + // { + // this.SetBasePath(path); + // UpdateConfiguration(); + // } + + // internal void ChangeFileProvider(IFileProvider fileProvider) + // { + // this.SetFileProvider(fileProvider); + // UpdateConfiguration(); + // } + + // //private void AttachReloadToken() + // //{ + // // // We dispose the IConfigurationRoot every time we reattach so we don't bother tracking the registration. + // // _configuration.GetReloadToken().RegisterChangeCallback(static state => + // // ((ConfigurationReloadToken)state).OnReload(), _reloadToken); + // //} + + // private void UpdateConfiguration() + // { + // var current = _configuration; + // if (current is IDisposable disposable) + // { + // disposable.Dispose(); + // } + // _configuration = _builder.Build(); + // } + + // IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) + // { + // Sources.Add(source); + // return this; + // } + + // IConfigurationRoot IConfigurationBuilder.Build() + // { + // // No more modification is expected after this final build + // UpdateConfiguration(); + // return this; + // } + + // IChangeToken IConfiguration.GetReloadToken() + // { + // return _configuration.GetReloadToken(); + // } + + // void IConfigurationRoot.Reload() + // { + // _configuration.Reload(); + // UpdateConfiguration(); + // } + + // // On source modifications, we rebuild configuration + // private class ConfigurationSources : IList + // { + // private readonly IList _sources; + // private readonly Configuration _config; + + // public ConfigurationSources(IList sources, Configuration config) + // { + // _sources = sources; + // _config = config; + // } + + // public IConfigurationSource this[int index] + // { + // get => _sources[index]; + // set + // { + // _sources[index] = value; + // _config.UpdateConfiguration(); + // } + // } + + // public int Count => _sources.Count; + + // public bool IsReadOnly => _sources.IsReadOnly; + + // public void Add(IConfigurationSource item) + // { + // _sources.Add(item); + // _config.UpdateConfiguration(); + // } + + // public void Clear() + // { + // _sources.Clear(); + // _config.UpdateConfiguration(); + // } + + // public bool Contains(IConfigurationSource item) + // { + // return _sources.Contains(item); + // } + + // public void CopyTo(IConfigurationSource[] array, int arrayIndex) + // { + // _sources.CopyTo(array, arrayIndex); + // } + + // public IEnumerator GetEnumerator() + // { + // return _sources.GetEnumerator(); + // } + + // public int IndexOf(IConfigurationSource item) + // { + // return _sources.IndexOf(item); + // } + + // public void Insert(int index, IConfigurationSource item) + // { + // _sources.Insert(index, item); + // _config.UpdateConfiguration(); + // } + + // public bool Remove(IConfigurationSource item) + // { + // var removed = _sources.Remove(item); + // _config.UpdateConfiguration(); + // return removed; + // } + + // public void RemoveAt(int index) + // { + // _sources.RemoveAt(index); + // _config.UpdateConfiguration(); + // } + + // IEnumerator IEnumerable.GetEnumerator() + // { + // return GetEnumerator(); + // } + // } + /// /// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot. /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. /// - public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder + public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, IDisposable { - private readonly ConfigurationBuilder _builder = new(); - private IConfigurationRoot _configuration; - - /// - /// Gets or sets a configuration value. - /// - /// The configuration key. - /// The configuration value. - public string this[string key] { get => _configuration[key]; set => _configuration[key] = value; } - - /// - /// Gets a configuration sub-section with the specified key. - /// - /// The key of the configuration section. - /// The . - /// - /// This method will never return null. If no matching sub-section is found with the specified key, - /// an empty will be returned. - /// - public IConfigurationSection GetSection(string key) + // We start off dirty to ensure the first configuration root is built on demand + private bool _dirty = true; + private ConfigurationRoot? _configurationRoot; + private ConfigurationReloadToken _changeToken = new(); + private IDisposable? _changeTokenRegistration; + + // Needs to be a separate field, as it is not possible to initialize an explicitly implemented property in the constructor + // and we pass an instance method to the constructor used to initialize it, so cannot use a field initializer. + // On the other hand, if this is a separate class (not an enhanced version of the existing ConfigurationBuilder class) + // then we probably do want `Properties` to be an explicit interface implementation, so users looking to read configuration values + // don't get confused by it. So explicit backing field it is! + private readonly IDictionary _properties; + + private IConfigurationRoot ConfigurationRoot { - return _configuration.GetSection(key); + get + { + EnsureFreshConfiguration(); + Debug.Assert(_configurationRoot is not null); + return _configurationRoot; + } } - /// - /// Gets the immediate descendant configuration sub-sections. - /// - /// The configuration sub-sections. - public IEnumerable GetChildren() => _configuration.GetChildren(); + public string this[string key] { get => ConfigurationRoot[key]; set => ConfigurationRoot[key] = value; } - IDictionary IConfigurationBuilder.Properties => _builder.Properties; + public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); - IList IConfigurationBuilder.Sources => Sources; + public IEnumerable GetChildren() => GetChildrenImplementation(null); + + IDictionary IConfigurationBuilder.Properties => _properties; internal IList Sources { get; } + IList IConfigurationBuilder.Sources => Sources; - IEnumerable IConfigurationRoot.Providers => _configuration.Providers; + IEnumerable IConfigurationRoot.Providers => ConfigurationRoot.Providers; - /// - /// Creates a new . - /// public Configuration() { - _configuration = _builder.Build(); - - var sources = new ConfigurationSources(_builder.Sources, UpdateConfigurationRoot); - - Sources = sources; + _properties = new ConfigurationProperties(this); + Sources = new ConfigurationSources(this); } - internal void ChangeBasePath(string path) + public void Dispose() { - this.SetBasePath(path); - UpdateConfigurationRoot(); + _changeTokenRegistration?.Dispose(); + _configurationRoot?.Dispose(); } - internal void ChangeFileProvider(IFileProvider fileProvider) + IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) { - this.SetFileProvider(fileProvider); - UpdateConfigurationRoot(); + Sources.Add(source ?? throw new ArgumentNullException(nameof(source))); + return this; } - private void UpdateConfigurationRoot() + IConfigurationRoot IConfigurationBuilder.Build() => BuildConfigurationRoot(); + + IChangeToken IConfiguration.GetReloadToken() => _changeToken; + + void IConfigurationRoot.Reload() => ConfigurationRoot.Reload(); + + private void MarkDirty() => _dirty = true; + + private void EnsureFreshConfiguration() { - var current = _configuration; - if (current is IDisposable disposable) + if (!_dirty) { - disposable.Dispose(); + return; } - _configuration = _builder.Build(); - } - IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) - { - Sources.Add(source); - return this; + var newConfiguration = BuildConfigurationRoot(); + var prevConfiguration = _configurationRoot; + + _configurationRoot = newConfiguration; + _dirty = false; + + _changeTokenRegistration?.Dispose(); + (prevConfiguration as IDisposable)?.Dispose(); + + _changeTokenRegistration = ChangeToken.OnChange(() => newConfiguration.GetReloadToken(), RaiseChanged); + RaiseChanged(); } - IConfigurationRoot IConfigurationBuilder.Build() + private ConfigurationRoot BuildConfigurationRoot() { - // No more modification is expected after this final build - UpdateConfigurationRoot(); - return this; + var providers = new List(); + foreach (var source in Sources) + { + IConfigurationProvider provider = source.Build(this); + providers.Add(provider); + } + return new ConfigurationRoot(providers); } - IChangeToken IConfiguration.GetReloadToken() + private void RaiseChanged() { - // REVIEW: Is this correct? - return _configuration.GetReloadToken(); + ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); + previousToken.OnReload(); } - void IConfigurationRoot.Reload() + /// + /// Gets the immediate children sub-sections of configuration root based on key. + /// + /// Key of a section of which children to retrieve. + /// Immediate children sub-sections of section specified by key. + private IEnumerable GetChildrenImplementation(string? path) { - _configuration.Reload(); + // From https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs + return ConfigurationRoot.Providers + .Aggregate(Enumerable.Empty(), + (seed, source) => source.GetChildKeys(seed, path)) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(key => ConfigurationRoot.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); } - // On source modifications, we rebuild configuration + // On source modifications, we mark configuration as dirty private class ConfigurationSources : IList { private readonly IList _sources; - private readonly Action _sourcesModified; + private readonly Configuration _config; - public ConfigurationSources(IList sources, Action sourcesModified) + public ConfigurationSources(Configuration config) { - _sources = sources; - _sourcesModified = sourcesModified; + _sources = new List(); + _config = config; } public IConfigurationSource this[int index] @@ -131,7 +341,7 @@ public IConfigurationSource this[int index] set { _sources[index] = value; - _sourcesModified(); + _config.MarkDirty(); } } @@ -142,13 +352,13 @@ public IConfigurationSource this[int index] public void Add(IConfigurationSource item) { _sources.Add(item); - _sourcesModified(); + _config.MarkDirty(); } public void Clear() { _sources.Clear(); - _sourcesModified(); + _config.MarkDirty(); } public bool Contains(IConfigurationSource item) @@ -174,20 +384,20 @@ public int IndexOf(IConfigurationSource item) public void Insert(int index, IConfigurationSource item) { _sources.Insert(index, item); - _sourcesModified(); + _config.MarkDirty(); } public bool Remove(IConfigurationSource item) { var removed = _sources.Remove(item); - _sourcesModified(); + _config.MarkDirty(); return removed; } public void RemoveAt(int index) { _sources.RemoveAt(index); - _sourcesModified(); + _config.MarkDirty(); } IEnumerator IEnumerable.GetEnumerator() @@ -195,5 +405,78 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } } + + // On property modifications we mark configuration as dirty. + private class ConfigurationProperties : IDictionary + { + private readonly IDictionary _properties = new Dictionary(); + private readonly Configuration _config; + + public ConfigurationProperties(Configuration config) + { + _config = config; + } + + public object this[string key] + { + get => _properties[key]; + set + { + _config.MarkDirty(); + _properties[key] = value; + } + } + + public ICollection Keys => _properties.Keys; + + public ICollection Values => _properties.Values; + + public int Count => _properties.Count; + + public bool IsReadOnly => false; + + public void Add(string key, object value) + { + _properties.Add(key, value); + _config.MarkDirty(); + } + + public void Add(KeyValuePair item) + { + _properties.Add(item); + _config.MarkDirty(); + } + + public void Clear() + { + _properties.Clear(); + _config.MarkDirty(); + } + + public bool Contains(KeyValuePair item) => _properties.Contains(item); + + public bool ContainsKey(string key) => _properties.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) => _properties.CopyTo(array, arrayIndex); + + public IEnumerator> GetEnumerator() => _properties.GetEnumerator(); + + public bool Remove(string key) + { + var removed = _properties.Remove(key); + _config.MarkDirty(); + return removed; + } + + public bool Remove(KeyValuePair item) + { + var removed = _properties.Remove(item); + _config.MarkDirty(); + return removed; + } + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out object value) => _properties.TryGetValue(key, out value); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_properties).GetEnumerator(); + } } } diff --git a/src/DefaultBuilder/src/ConfigureHostBuilder.cs b/src/DefaultBuilder/src/ConfigureHostBuilder.cs index 4f45e9a837a4..ce8b83777b41 100644 --- a/src/DefaultBuilder/src/ConfigureHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureHostBuilder.cs @@ -61,7 +61,7 @@ public IHostBuilder ConfigureHostConfiguration(Action con configureDelegate(_hostConfiguration); _environment.ApplyConfigurationSettings(_hostConfiguration.Build()); - Configuration.ChangeFileProvider(_environment.ContentRootFileProvider); + //Configuration.ChangeFileProvider(_environment.ContentRootFileProvider); return this; } diff --git a/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs b/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs index 5d95319e7738..74fc802923fa 100644 --- a/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs @@ -87,7 +87,7 @@ public IWebHostBuilder UseSetting(string key, string? value) _environment.ContentRootPath = value; _environment.ResolveFileProviders(_configuration); - _configuration.ChangeBasePath(value); + //_configuration.ChangeBasePath(value); } else if (string.Equals(key, WebHostDefaults.EnvironmentKey, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/DefaultBuilder/src/PublicAPI.Unshipped.txt b/src/DefaultBuilder/src/PublicAPI.Unshipped.txt index 3c537d7e886b..47d57f8126c4 100644 --- a/src/DefaultBuilder/src/PublicAPI.Unshipped.txt +++ b/src/DefaultBuilder/src/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable Microsoft.AspNetCore.Builder.Configuration Microsoft.AspNetCore.Builder.Configuration.Configuration() -> void +Microsoft.AspNetCore.Builder.Configuration.Dispose() -> void Microsoft.AspNetCore.Builder.Configuration.GetChildren() -> System.Collections.Generic.IEnumerable! Microsoft.AspNetCore.Builder.Configuration.GetSection(string! key) -> Microsoft.Extensions.Configuration.IConfigurationSection! Microsoft.AspNetCore.Builder.Configuration.this[string! key].get -> string! From 81d689af06681b49658b7eb1d62bfbf18d507c08 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 14 May 2021 14:51:52 -0700 Subject: [PATCH 05/10] Correct early config load order --- src/DefaultBuilder/src/BootstrapHostBuilder.cs | 3 ++- src/DefaultBuilder/src/WebApplicationBuilder.cs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/DefaultBuilder/src/BootstrapHostBuilder.cs b/src/DefaultBuilder/src/BootstrapHostBuilder.cs index 7b866620abf8..712f62fefb84 100644 --- a/src/DefaultBuilder/src/BootstrapHostBuilder.cs +++ b/src/DefaultBuilder/src/BootstrapHostBuilder.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Hosting // This exists solely to bootstrap the configuration internal class BootstrapHostBuilder : IHostBuilder { - public IDictionary Properties { get; } = new Dictionary(); private readonly HostBuilderContext _context; private readonly Configuration _configuration; private readonly WebHostEnvironment _environment; @@ -29,6 +28,8 @@ public BootstrapHostBuilder(Configuration configuration, WebHostEnvironment webH }; } + public IDictionary Properties { get; } = new Dictionary(); + public IHost Build() { // HostingHostBuilderExtensions.ConfigureDefaults should never call this. diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index e82aa0eba1fa..2c044a36bfe6 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -28,20 +28,23 @@ internal WebApplicationBuilder(Assembly? callingAssembly, string[]? args = null) // HACK: MVC and Identity do this horrible thing to get the hosting environment as an instance // from the service collection before it is built. That needs to be fixed... Environment = _environment = new WebHostEnvironment(callingAssembly); + Services.AddSingleton(Environment); // Run methods to configure both generic and web host defaults early to populate config from appsettings.json // environment variables (both DOTNET_ and ASPNETCORE_ prefixed) and other possible default sources to prepopulate // the correct defaults. var bootstrapBuilder = new BootstrapHostBuilder(Configuration, _environment); - bootstrapBuilder.ConfigureDefaults(args); bootstrapBuilder.ConfigureWebHostDefaults(configure: _ => { }); + bootstrapBuilder.ConfigureDefaults(args); Configuration.SetBasePath(_environment.ContentRootPath); Logging = new LoggingBuilder(Services); WebHost = _deferredWebHostBuilder = new ConfigureWebHostBuilder(Configuration, _environment, Services); Host = _deferredHostBuilder = new ConfigureHostBuilder(Configuration, _environment, Services); + Services.AddSingleton(Configuration); + _deferredHostBuilder.ConfigureDefaults(args); } From b6a75f7289dff0d8f7a9852a9d78ce31f4dc1de9 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 14 May 2021 14:55:16 -0700 Subject: [PATCH 06/10] Immediately RaiseChanged on change --- .../src/BootstrapHostBuilder.cs | 2 - src/DefaultBuilder/src/Configuration.cs | 230 ++---------------- .../src/ConfigureHostBuilder.cs | 1 - .../src/ConfigureWebHostBuilder.cs | 2 - 4 files changed, 14 insertions(+), 221 deletions(-) diff --git a/src/DefaultBuilder/src/BootstrapHostBuilder.cs b/src/DefaultBuilder/src/BootstrapHostBuilder.cs index 712f62fefb84..0fd8d16444aa 100644 --- a/src/DefaultBuilder/src/BootstrapHostBuilder.cs +++ b/src/DefaultBuilder/src/BootstrapHostBuilder.cs @@ -40,7 +40,6 @@ public IHostBuilder ConfigureAppConfiguration(Action con { configureDelegate(_configuration); _environment.ApplyConfigurationSettings(_configuration); - //_configuration.ChangeBasePath(_environment.ContentRootPath); return this; } diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index 3f13df68d3a2..9fc189e2071b 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -9,208 +9,16 @@ using System.Linq; using System.Threading; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Builder { - ///// - ///// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot. - ///// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. - ///// - //public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder - //{ - // private readonly ConfigurationBuilder _builder = new(); - // private IConfigurationRoot _configuration; - - // /// - // /// Gets or sets a configuration value. - // /// - // /// The configuration key. - // /// The configuration value. - // public string this[string key] { get => _configuration[key]; set => _configuration[key] = value; } - - // /// - // /// Gets a configuration sub-section with the specified key. - // /// - // /// The key of the configuration section. - // /// The . - // /// - // /// This method will never return null. If no matching sub-section is found with the specified key, - // /// an empty will be returned. - // /// - // public IConfigurationSection GetSection(string key) - // { - // return _configuration.GetSection(key); - // } - - // /// - // /// Gets the immediate descendant configuration sub-sections. - // /// - // /// The configuration sub-sections. - // public IEnumerable GetChildren() => _configuration.GetChildren(); - - // IDictionary IConfigurationBuilder.Properties => _builder.Properties; - - // IList IConfigurationBuilder.Sources => Sources; - - // internal IList Sources { get; } - - // IEnumerable IConfigurationRoot.Providers => _configuration.Providers; - - // /// - // /// Creates a new . - // /// - // public Configuration() - // { - // _configuration = _builder.Build(); - // Sources = new ConfigurationSources(_builder.Sources, this); - // } - - // internal void ChangeBasePath(string path) - // { - // this.SetBasePath(path); - // UpdateConfiguration(); - // } - - // internal void ChangeFileProvider(IFileProvider fileProvider) - // { - // this.SetFileProvider(fileProvider); - // UpdateConfiguration(); - // } - - // //private void AttachReloadToken() - // //{ - // // // We dispose the IConfigurationRoot every time we reattach so we don't bother tracking the registration. - // // _configuration.GetReloadToken().RegisterChangeCallback(static state => - // // ((ConfigurationReloadToken)state).OnReload(), _reloadToken); - // //} - - // private void UpdateConfiguration() - // { - // var current = _configuration; - // if (current is IDisposable disposable) - // { - // disposable.Dispose(); - // } - // _configuration = _builder.Build(); - // } - - // IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) - // { - // Sources.Add(source); - // return this; - // } - - // IConfigurationRoot IConfigurationBuilder.Build() - // { - // // No more modification is expected after this final build - // UpdateConfiguration(); - // return this; - // } - - // IChangeToken IConfiguration.GetReloadToken() - // { - // return _configuration.GetReloadToken(); - // } - - // void IConfigurationRoot.Reload() - // { - // _configuration.Reload(); - // UpdateConfiguration(); - // } - - // // On source modifications, we rebuild configuration - // private class ConfigurationSources : IList - // { - // private readonly IList _sources; - // private readonly Configuration _config; - - // public ConfigurationSources(IList sources, Configuration config) - // { - // _sources = sources; - // _config = config; - // } - - // public IConfigurationSource this[int index] - // { - // get => _sources[index]; - // set - // { - // _sources[index] = value; - // _config.UpdateConfiguration(); - // } - // } - - // public int Count => _sources.Count; - - // public bool IsReadOnly => _sources.IsReadOnly; - - // public void Add(IConfigurationSource item) - // { - // _sources.Add(item); - // _config.UpdateConfiguration(); - // } - - // public void Clear() - // { - // _sources.Clear(); - // _config.UpdateConfiguration(); - // } - - // public bool Contains(IConfigurationSource item) - // { - // return _sources.Contains(item); - // } - - // public void CopyTo(IConfigurationSource[] array, int arrayIndex) - // { - // _sources.CopyTo(array, arrayIndex); - // } - - // public IEnumerator GetEnumerator() - // { - // return _sources.GetEnumerator(); - // } - - // public int IndexOf(IConfigurationSource item) - // { - // return _sources.IndexOf(item); - // } - - // public void Insert(int index, IConfigurationSource item) - // { - // _sources.Insert(index, item); - // _config.UpdateConfiguration(); - // } - - // public bool Remove(IConfigurationSource item) - // { - // var removed = _sources.Remove(item); - // _config.UpdateConfiguration(); - // return removed; - // } - - // public void RemoveAt(int index) - // { - // _sources.RemoveAt(index); - // _config.UpdateConfiguration(); - // } - - // IEnumerator IEnumerable.GetEnumerator() - // { - // return GetEnumerator(); - // } - // } - /// /// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot. /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. /// public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, IDisposable { - // We start off dirty to ensure the first configuration root is built on demand - private bool _dirty = true; private ConfigurationRoot? _configurationRoot; private ConfigurationReloadToken _changeToken = new(); private IDisposable? _changeTokenRegistration; @@ -226,7 +34,7 @@ private IConfigurationRoot ConfigurationRoot { get { - EnsureFreshConfiguration(); + UpdateConfiguration(); Debug.Assert(_configurationRoot is not null); return _configurationRoot; } @@ -269,20 +77,12 @@ IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) void IConfigurationRoot.Reload() => ConfigurationRoot.Reload(); - private void MarkDirty() => _dirty = true; - - private void EnsureFreshConfiguration() + private void UpdateConfiguration() { - if (!_dirty) - { - return; - } - var newConfiguration = BuildConfigurationRoot(); var prevConfiguration = _configurationRoot; _configurationRoot = newConfiguration; - _dirty = false; _changeTokenRegistration?.Dispose(); (prevConfiguration as IDisposable)?.Dispose(); @@ -323,7 +123,6 @@ private IEnumerable GetChildrenImplementation(string? pat .Select(key => ConfigurationRoot.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); } - // On source modifications, we mark configuration as dirty private class ConfigurationSources : IList { private readonly IList _sources; @@ -341,7 +140,7 @@ public IConfigurationSource this[int index] set { _sources[index] = value; - _config.MarkDirty(); + _config.UpdateConfiguration(); } } @@ -352,13 +151,13 @@ public IConfigurationSource this[int index] public void Add(IConfigurationSource item) { _sources.Add(item); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public void Clear() { _sources.Clear(); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public bool Contains(IConfigurationSource item) @@ -384,20 +183,20 @@ public int IndexOf(IConfigurationSource item) public void Insert(int index, IConfigurationSource item) { _sources.Insert(index, item); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public bool Remove(IConfigurationSource item) { var removed = _sources.Remove(item); - _config.MarkDirty(); + _config.UpdateConfiguration(); return removed; } public void RemoveAt(int index) { _sources.RemoveAt(index); - _config.MarkDirty(); + _config.UpdateConfiguration(); } IEnumerator IEnumerable.GetEnumerator() @@ -406,7 +205,6 @@ IEnumerator IEnumerable.GetEnumerator() } } - // On property modifications we mark configuration as dirty. private class ConfigurationProperties : IDictionary { private readonly IDictionary _properties = new Dictionary(); @@ -422,7 +220,7 @@ public object this[string key] get => _properties[key]; set { - _config.MarkDirty(); + _config.UpdateConfiguration(); _properties[key] = value; } } @@ -438,19 +236,19 @@ public object this[string key] public void Add(string key, object value) { _properties.Add(key, value); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public void Add(KeyValuePair item) { _properties.Add(item); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public void Clear() { _properties.Clear(); - _config.MarkDirty(); + _config.UpdateConfiguration(); } public bool Contains(KeyValuePair item) => _properties.Contains(item); @@ -464,14 +262,14 @@ public void Clear() public bool Remove(string key) { var removed = _properties.Remove(key); - _config.MarkDirty(); + _config.UpdateConfiguration(); return removed; } public bool Remove(KeyValuePair item) { var removed = _properties.Remove(item); - _config.MarkDirty(); + _config.UpdateConfiguration(); return removed; } diff --git a/src/DefaultBuilder/src/ConfigureHostBuilder.cs b/src/DefaultBuilder/src/ConfigureHostBuilder.cs index ce8b83777b41..7de61e95f95a 100644 --- a/src/DefaultBuilder/src/ConfigureHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureHostBuilder.cs @@ -61,7 +61,6 @@ public IHostBuilder ConfigureHostConfiguration(Action con configureDelegate(_hostConfiguration); _environment.ApplyConfigurationSettings(_hostConfiguration.Build()); - //Configuration.ChangeFileProvider(_environment.ContentRootFileProvider); return this; } diff --git a/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs b/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs index 74fc802923fa..043b590777c5 100644 --- a/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureWebHostBuilder.cs @@ -86,8 +86,6 @@ public IWebHostBuilder UseSetting(string key, string? value) { _environment.ContentRootPath = value; _environment.ResolveFileProviders(_configuration); - - //_configuration.ChangeBasePath(value); } else if (string.Equals(key, WebHostDefaults.EnvironmentKey, StringComparison.OrdinalIgnoreCase)) { From 0fca5ca4e5097530df978a3e0b16870cb336eaa6 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 18 May 2021 10:29:27 -0700 Subject: [PATCH 07/10] Add doc comments --- src/DefaultBuilder/src/Configuration.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index 9fc189e2071b..672edbdbb7e8 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Builder { /// - /// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot. + /// Configuration is mutable configuration object. It is both an and an . /// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen. /// public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, IDisposable @@ -40,10 +40,13 @@ private IConfigurationRoot ConfigurationRoot } } + /// public string this[string key] { get => ConfigurationRoot[key]; set => ConfigurationRoot[key] = value; } + /// public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); + /// public IEnumerable GetChildren() => GetChildrenImplementation(null); IDictionary IConfigurationBuilder.Properties => _properties; @@ -53,12 +56,16 @@ private IConfigurationRoot ConfigurationRoot IEnumerable IConfigurationRoot.Providers => ConfigurationRoot.Providers; + /// + /// Creates an empty mutable configuration object that is both an and an . + /// public Configuration() { _properties = new ConfigurationProperties(this); Sources = new ConfigurationSources(this); } + /// public void Dispose() { _changeTokenRegistration?.Dispose(); From 866aec7defe528f884375423b3dcf02efb698248 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 18 May 2021 12:01:28 -0700 Subject: [PATCH 08/10] cleanup --- src/DefaultBuilder/src/Configuration.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index 672edbdbb7e8..eb982161571f 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -19,17 +19,13 @@ namespace Microsoft.AspNetCore.Builder /// public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, IDisposable { + private readonly ConfigurationSources _sources; + private readonly IDictionary _properties; + private ConfigurationRoot? _configurationRoot; private ConfigurationReloadToken _changeToken = new(); private IDisposable? _changeTokenRegistration; - // Needs to be a separate field, as it is not possible to initialize an explicitly implemented property in the constructor - // and we pass an instance method to the constructor used to initialize it, so cannot use a field initializer. - // On the other hand, if this is a separate class (not an enhanced version of the existing ConfigurationBuilder class) - // then we probably do want `Properties` to be an explicit interface implementation, so users looking to read configuration values - // don't get confused by it. So explicit backing field it is! - private readonly IDictionary _properties; - private IConfigurationRoot ConfigurationRoot { get @@ -51,8 +47,7 @@ private IConfigurationRoot ConfigurationRoot IDictionary IConfigurationBuilder.Properties => _properties; - internal IList Sources { get; } - IList IConfigurationBuilder.Sources => Sources; + IList IConfigurationBuilder.Sources => _sources; IEnumerable IConfigurationRoot.Providers => ConfigurationRoot.Providers; @@ -62,7 +57,7 @@ private IConfigurationRoot ConfigurationRoot public Configuration() { _properties = new ConfigurationProperties(this); - Sources = new ConfigurationSources(this); + _sources = new ConfigurationSources(this); } /// @@ -74,7 +69,7 @@ public void Dispose() IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) { - Sources.Add(source ?? throw new ArgumentNullException(nameof(source))); + _sources.Add(source ?? throw new ArgumentNullException(nameof(source))); return this; } @@ -101,7 +96,7 @@ private void UpdateConfiguration() private ConfigurationRoot BuildConfigurationRoot() { var providers = new List(); - foreach (var source in Sources) + foreach (var source in _sources) { IConfigurationProvider provider = source.Build(this); providers.Add(provider); From a52a3d35befd20c20ca2e9eecf71699622499107 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Tue, 18 May 2021 12:03:49 -0700 Subject: [PATCH 09/10] Only update UpdateConfiguration on modification --- src/DefaultBuilder/src/Configuration.cs | 29 +++++++------------ .../src/WebApplicationBuilder.cs | 2 +- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/DefaultBuilder/src/Configuration.cs b/src/DefaultBuilder/src/Configuration.cs index eb982161571f..eaeec3d8aac4 100644 --- a/src/DefaultBuilder/src/Configuration.cs +++ b/src/DefaultBuilder/src/Configuration.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; @@ -21,23 +20,13 @@ public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, I { private readonly ConfigurationSources _sources; private readonly IDictionary _properties; + private ConfigurationRoot _configurationRoot; - private ConfigurationRoot? _configurationRoot; private ConfigurationReloadToken _changeToken = new(); private IDisposable? _changeTokenRegistration; - private IConfigurationRoot ConfigurationRoot - { - get - { - UpdateConfiguration(); - Debug.Assert(_configurationRoot is not null); - return _configurationRoot; - } - } - /// - public string this[string key] { get => ConfigurationRoot[key]; set => ConfigurationRoot[key] = value; } + public string this[string key] { get => _configurationRoot[key]; set => _configurationRoot[key] = value; } /// public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key); @@ -49,7 +38,7 @@ private IConfigurationRoot ConfigurationRoot IList IConfigurationBuilder.Sources => _sources; - IEnumerable IConfigurationRoot.Providers => ConfigurationRoot.Providers; + IEnumerable IConfigurationRoot.Providers => _configurationRoot.Providers; /// /// Creates an empty mutable configuration object that is both an and an . @@ -58,6 +47,10 @@ public Configuration() { _properties = new ConfigurationProperties(this); _sources = new ConfigurationSources(this); + + // _configurationRoot is set by UpdateConfiguration() + _configurationRoot = default!; + UpdateConfiguration(); } /// @@ -77,7 +70,7 @@ IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source) IChangeToken IConfiguration.GetReloadToken() => _changeToken; - void IConfigurationRoot.Reload() => ConfigurationRoot.Reload(); + void IConfigurationRoot.Reload() => _configurationRoot.Reload(); private void UpdateConfiguration() { @@ -118,11 +111,11 @@ private void RaiseChanged() private IEnumerable GetChildrenImplementation(string? path) { // From https://github.com/dotnet/runtime/blob/01b7e73cd378145264a7cb7a09365b41ed42b240/src/libraries/Microsoft.Extensions.Configuration/src/InternalConfigurationRootExtensions.cs - return ConfigurationRoot.Providers + return _configurationRoot.Providers .Aggregate(Enumerable.Empty(), (seed, source) => source.GetChildKeys(seed, path)) .Distinct(StringComparer.OrdinalIgnoreCase) - .Select(key => ConfigurationRoot.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); + .Select(key => _configurationRoot.GetSection(path == null ? key : ConfigurationPath.Combine(path, key))); } private class ConfigurationSources : IList @@ -222,8 +215,8 @@ public object this[string key] get => _properties[key]; set { - _config.UpdateConfiguration(); _properties[key] = value; + _config.UpdateConfiguration(); } } diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 2c044a36bfe6..f50c0db9bce8 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -174,7 +174,7 @@ private void ConfigureWebHost(IWebHostBuilder genericWebHostBuilder) // already thanks to the BootstrapHostBuilder. builder.Sources.Clear(); - foreach (var s in Configuration.Sources) + foreach (var s in ((IConfigurationBuilder)Configuration).Sources) { builder.Sources.Add(s); } From c7933fd64f6919d12439a41a1df40f9fa077cd34 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Thu, 20 May 2021 15:30:04 -0700 Subject: [PATCH 10/10] SetBasePath earlier. Copy sources once --- .../src/BootstrapHostBuilder.cs | 28 ++++++++++--- src/DefaultBuilder/src/Configuration.cs | 4 +- .../src/ConfigureHostBuilder.cs | 39 ++++++++++++------- .../src/WebApplicationBuilder.cs | 32 ++++++++------- 4 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/DefaultBuilder/src/BootstrapHostBuilder.cs b/src/DefaultBuilder/src/BootstrapHostBuilder.cs index 0fd8d16444aa..6a7f41eea64f 100644 --- a/src/DefaultBuilder/src/BootstrapHostBuilder.cs +++ b/src/DefaultBuilder/src/BootstrapHostBuilder.cs @@ -17,6 +17,9 @@ internal class BootstrapHostBuilder : IHostBuilder private readonly Configuration _configuration; private readonly WebHostEnvironment _environment; + private readonly List> _configureHostActions = new List>(); + private readonly List> _configureAppActions = new List>(); + public BootstrapHostBuilder(Configuration configuration, WebHostEnvironment webHostEnvironment) { _configuration = configuration; @@ -38,8 +41,7 @@ public IHost Build() public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) { - configureDelegate(_context, _configuration); - _environment.ApplyConfigurationSettings(_configuration); + _configureAppActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } @@ -52,8 +54,7 @@ public IHostBuilder ConfigureContainer(Action configureDelegate) { - configureDelegate(_configuration); - _environment.ApplyConfigurationSettings(_configuration); + _configureHostActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate))); return this; } @@ -66,7 +67,7 @@ public IHostBuilder ConfigureServices(Action(IServiceProviderFactory factory) where TContainerBuilder : notnull { - // This is not called by HostingHostBuilderExtensions.ConfigureDefaults currently, but that chould change in the future. + // This is not called by HostingHostBuilderExtensions.ConfigureDefaults currently, but that could change in the future. // If this does get called in the future, it should be called again at a later stage on the ConfigureHostBuilder. return this; } @@ -77,5 +78,22 @@ public IHostBuilder UseServiceProviderFactory(Func(); foreach (var source in _sources) { - IConfigurationProvider provider = source.Build(this); + var provider = source.Build(this); providers.Add(provider); } return new ConfigurationRoot(providers); @@ -99,7 +99,7 @@ private ConfigurationRoot BuildConfigurationRoot() private void RaiseChanged() { - ConfigurationReloadToken previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); + var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken()); previousToken.OnReload(); } diff --git a/src/DefaultBuilder/src/ConfigureHostBuilder.cs b/src/DefaultBuilder/src/ConfigureHostBuilder.cs index 7de61e95f95a..151d3b87061b 100644 --- a/src/DefaultBuilder/src/ConfigureHostBuilder.cs +++ b/src/DefaultBuilder/src/ConfigureHostBuilder.cs @@ -20,21 +20,27 @@ public sealed class ConfigureHostBuilder : IHostBuilder /// public IDictionary Properties { get; } = new Dictionary(); - internal Configuration Configuration => _configuration; - - private readonly IConfigurationBuilder _hostConfiguration = new ConfigurationBuilder(); - private readonly WebHostEnvironment _environment; private readonly Configuration _configuration; private readonly IServiceCollection _services; + private readonly HostBuilderContext _context; + internal ConfigureHostBuilder(Configuration configuration, WebHostEnvironment environment, IServiceCollection services) { _configuration = configuration; _environment = environment; _services = services; + + _context = new HostBuilderContext(Properties) + { + Configuration = _configuration, + HostingEnvironment = _environment + }; } + internal bool ConfigurationEnabled { get; set; } + IHost IHostBuilder.Build() { throw new NotSupportedException($"Call {nameof(WebApplicationBuilder)}.{nameof(WebApplicationBuilder.Build)}() instead."); @@ -43,7 +49,13 @@ IHost IHostBuilder.Build() /// public IHostBuilder ConfigureAppConfiguration(Action configureDelegate) { - _operations += b => b.ConfigureAppConfiguration(configureDelegate); + if (ConfigurationEnabled) + { + // Run these immediately so that they are observable by the imperative code + configureDelegate(_context, _configuration); + _environment.ApplyConfigurationSettings(_configuration); + } + return this; } @@ -57,10 +69,12 @@ public IHostBuilder ConfigureContainer(Action public IHostBuilder ConfigureHostConfiguration(Action configureDelegate) { - // HACK: We need to evaluate the host configuration as they are changes so that we have an accurate view of the world - configureDelegate(_hostConfiguration); - - _environment.ApplyConfigurationSettings(_hostConfiguration.Build()); + if (ConfigurationEnabled) + { + // Run these immediately so that they are observable by the imperative code + configureDelegate(_configuration); + _environment.ApplyConfigurationSettings(_configuration); + } return this; } @@ -69,12 +83,7 @@ public IHostBuilder ConfigureHostConfiguration(Action con public IHostBuilder ConfigureServices(Action configureDelegate) { // Run these immediately so that they are observable by the imperative code - configureDelegate(new HostBuilderContext(Properties) - { - Configuration = Configuration, - HostingEnvironment = _environment - }, - _services); + configureDelegate(_context, _services); return this; } diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index f50c0db9bce8..d4f021064ab1 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -29,23 +29,29 @@ internal WebApplicationBuilder(Assembly? callingAssembly, string[]? args = null) // from the service collection before it is built. That needs to be fixed... Environment = _environment = new WebHostEnvironment(callingAssembly); + Configuration.SetBasePath(_environment.ContentRootPath); Services.AddSingleton(Environment); // Run methods to configure both generic and web host defaults early to populate config from appsettings.json // environment variables (both DOTNET_ and ASPNETCORE_ prefixed) and other possible default sources to prepopulate // the correct defaults. var bootstrapBuilder = new BootstrapHostBuilder(Configuration, _environment); - bootstrapBuilder.ConfigureWebHostDefaults(configure: _ => { }); bootstrapBuilder.ConfigureDefaults(args); + bootstrapBuilder.ConfigureWebHostDefaults(configure: _ => { }); + bootstrapBuilder.ExecuteActions(); - Configuration.SetBasePath(_environment.ContentRootPath); Logging = new LoggingBuilder(Services); WebHost = _deferredWebHostBuilder = new ConfigureWebHostBuilder(Configuration, _environment, Services); Host = _deferredHostBuilder = new ConfigureHostBuilder(Configuration, _environment, Services); + // Register Configuration as IConfiguration so updates can be observed even after the WebApplication is built. Services.AddSingleton(Configuration); + // Add default services _deferredHostBuilder.ConfigureDefaults(args); + // Configuration changes made by ConfigureDefaults(args) were already picked up by the BootstrapHostBuilder, + // so we ignore changes to config until ConfigureDefaults completes. + _deferredHostBuilder.ConfigurationEnabled = true; } /// @@ -158,17 +164,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui private void ConfigureWebHost(IWebHostBuilder genericWebHostBuilder) { - genericWebHostBuilder.Configure(ConfigureApplication); - - _hostBuilder.ConfigureServices((context, services) => - { - foreach (var s in Services) - { - services.Add(s); - } - }); - - _hostBuilder.ConfigureAppConfiguration((hostContext, builder) => + _hostBuilder.ConfigureHostConfiguration(builder => { // All the sources in builder.Sources should be in Configuration.Sources // already thanks to the BootstrapHostBuilder. @@ -180,6 +176,16 @@ private void ConfigureWebHost(IWebHostBuilder genericWebHostBuilder) } }); + _hostBuilder.ConfigureServices((context, services) => + { + foreach (var s in Services) + { + services.Add(s); + } + }); + + genericWebHostBuilder.Configure(ConfigureApplication); + _deferredHostBuilder.ExecuteActions(_hostBuilder); _deferredWebHostBuilder.ExecuteActions(genericWebHostBuilder);