From ae47bb21a69f85aeaeec0b7962738cd6b465f090 Mon Sep 17 00:00:00 2001 From: John Luo Date: Mon, 25 Jan 2016 17:32:22 -0800 Subject: [PATCH] Ordering sensitive configuration #582 --- .../IWebHostBuilder.cs | 25 ++--- .../Internal/IConfigurationExtensions.cs | 34 +++++++ .../{ => Internal}/WebHostConfiguration.cs | 2 +- .../WebHostBuilder.cs | 40 ++------ .../WebHostBuilderExtensions.cs | 11 +++ .../TestServer.cs | 2 +- .../WebHostBuilderTests.cs | 95 ++++++++++++++++++- 7 files changed, 156 insertions(+), 53 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Hosting/Internal/IConfigurationExtensions.cs rename src/Microsoft.AspNetCore.Hosting/{ => Internal}/WebHostConfiguration.cs (96%) diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs index 35f9950567..1b7510cf7e 100644 --- a/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs @@ -2,10 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Hosting @@ -19,20 +17,6 @@ namespace Microsoft.AspNetCore.Hosting /// Builds an which hosts a web application. /// IWebHost Build(); - - /// - /// Gets the raw settings to be used by the web host. Values specified here will override - /// the configuration set by . - /// - IDictionary Settings { get; } - - /// - /// Specify the to be used by the web host. If no configuration is - /// provided to the builder, the default configuration will be used. - /// - /// The to be used. - /// The . - IWebHostBuilder UseConfiguration(IConfiguration configuration); /// /// Specify the to be used by the web host. @@ -63,11 +47,18 @@ namespace Microsoft.AspNetCore.Hosting IWebHostBuilder Configure(Action configureApplication); /// - /// Add or replace a setting in . + /// Add or replace a setting in the configuration. /// /// The key of the setting to add or replace. /// The value of the setting to add or replace. /// The . IWebHostBuilder UseSetting(string key, string value); + + /// + /// Get the setting value from the configuration. + /// + /// The key of the setting to look up. + /// The value the setting currently contains. + string GetSetting(string key); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/IConfigurationExtensions.cs b/src/Microsoft.AspNetCore.Hosting/Internal/IConfigurationExtensions.cs new file mode 100644 index 0000000000..e1565878ac --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting/Internal/IConfigurationExtensions.cs @@ -0,0 +1,34 @@ +// 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.Collections.Generic; +using Microsoft.Extensions.Configuration; + +namespace Microsoft.AspNetCore.Hosting.Internal +{ + public static class IConfigurationExtensions + { + // Temporary until Configuration/issues/370 is implemented. + public static IEnumerable> GetFlattenedSettings(this IConfiguration configuration) + { + var stack = new Stack(); + stack.Push(configuration); + + while (stack.Count > 0) + { + var config = stack.Pop(); + var section = config as IConfigurationSection; + + if (section != null) + { + yield return new KeyValuePair(section.Path, section.Value); + } + + foreach (var child in config.GetChildren()) + { + stack.Push(child); + } + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostConfiguration.cs b/src/Microsoft.AspNetCore.Hosting/Internal/WebHostConfiguration.cs similarity index 96% rename from src/Microsoft.AspNetCore.Hosting/WebHostConfiguration.cs rename to src/Microsoft.AspNetCore.Hosting/Internal/WebHostConfiguration.cs index 3a6cd02a4e..65639d3146 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostConfiguration.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/WebHostConfiguration.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Microsoft.Extensions.Configuration; -namespace Microsoft.AspNetCore.Hosting +namespace Microsoft.AspNetCore.Hosting.Internal { public class WebHostConfiguration { diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs index 46ebe789fd..41dc917029 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.Versioning; using Microsoft.AspNetCore.Builder; @@ -28,7 +27,7 @@ namespace Microsoft.AspNetCore.Hosting private readonly IHostingEnvironment _hostingEnvironment; private readonly ILoggerFactory _loggerFactory; - private IConfiguration _config; + private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build(); private WebHostOptions _options; private Action _configureServices; @@ -40,26 +39,12 @@ namespace Microsoft.AspNetCore.Hosting // Only one of these should be set private IServerFactory _serverFactory; - private IDictionary _settings = new Dictionary(StringComparer.OrdinalIgnoreCase); - public WebHostBuilder() { _hostingEnvironment = new HostingEnvironment(); _loggerFactory = new LoggerFactory(); } - /// - /// Gets the raw settings to be used by the web host. Values specified here will override - /// the configuration set by . - /// - public IDictionary Settings - { - get - { - return _settings; - } - } - /// /// Add or replace a setting in . /// @@ -68,20 +53,18 @@ namespace Microsoft.AspNetCore.Hosting /// The . public IWebHostBuilder UseSetting(string key, string value) { - _settings[key] = value; + _config[key] = value; return this; } /// - /// Specify the to be used by the web host. If no configuration is - /// provided to the builder, the default configuration will be used. + /// Get the setting value from the configuration. /// - /// The to be used. - /// The . - public IWebHostBuilder UseConfiguration(IConfiguration configuration) + /// The key of the setting to look up. + /// The value the setting currently contains. + public string GetSetting(string key) { - _config = configuration; - return this; + return _config[key]; } /// @@ -187,15 +170,6 @@ namespace Microsoft.AspNetCore.Hosting private IServiceCollection BuildHostingServices() { - // Apply the configuration settings - var configuration = _config ?? WebHostConfiguration.GetDefault(); - - var mergedConfiguration = new ConfigurationBuilder() - .Add(new IncludedConfigurationProvider(configuration)) - .AddInMemoryCollection(_settings) - .Build(); - - _config = mergedConfiguration; _options = new WebHostOptions(_config); var services = new ServiceCollection(); diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs index 93a00ccd68..74141cf20d 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.Extensions.Configuration; @@ -21,6 +22,16 @@ namespace Microsoft.AspNetCore.Hosting return builder.UseConfiguration(WebHostConfiguration.GetDefault(args)); } + public static IWebHostBuilder UseConfiguration(this IWebHostBuilder builder, IConfiguration configuration) + { + foreach (var setting in configuration.GetFlattenedSettings()) + { + builder.UseSetting(setting.Key, setting.Value); + } + + return builder; + } + public static IWebHostBuilder UseCaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupError) { return hostBuilder.UseSetting(WebHostDefaults.CaptureStartupErrorsKey, captureStartupError ? "true" : "false"); diff --git a/src/Microsoft.AspNetCore.TestHost/TestServer.cs b/src/Microsoft.AspNetCore.TestHost/TestServer.cs index 2b5e6e42f2..1d5f044333 100644 --- a/src/Microsoft.AspNetCore.TestHost/TestServer.cs +++ b/src/Microsoft.AspNetCore.TestHost/TestServer.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.TestHost public TestServer(IWebHostBuilder builder) { - if (!builder.Settings.ContainsKey(WebHostDefaults.CaptureStartupErrorsKey)) + if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.CaptureStartupErrorsKey))) { builder.UseCaptureStartupErrors(false); } diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index b468e52864..a3f60e73a3 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -124,9 +124,10 @@ namespace Microsoft.AspNetCore.Hosting } [Fact] - public void CaptureStartupErrorsByDefault() + public void DefaultConfigurationCapturesStartupErrors() { var hostBuilder = new WebHostBuilder() + .UseDefaultConfiguration() .UseServer(new TestServer()) .UseStartup(); @@ -146,6 +147,98 @@ namespace Microsoft.AspNetCore.Hosting Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message); } + [Fact] + public void CodeBasedSettingsCodeBasedOverride() + { + var hostBuilder = new WebHostBuilder() + .UseSetting(WebHostDefaults.ServerKey, "ServerA") + .UseSetting(WebHostDefaults.ServerKey, "ServerB") + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + + Assert.Equal("ServerB", host.ServerFactoryLocation); + } + + [Fact] + public void CodeBasedSettingsConfigBasedOverride() + { + var settings = new Dictionary + { + { WebHostDefaults.ServerKey, "ServerB" } + }; + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(settings) + .Build(); + + var hostBuilder = new WebHostBuilder() + .UseSetting(WebHostDefaults.ServerKey, "ServerA") + .UseConfiguration(config) + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + + Assert.Equal("ServerB", host.ServerFactoryLocation); + } + + [Fact] + public void ConfigBasedSettingsCodeBasedOverride() + { + var settings = new Dictionary + { + { WebHostDefaults.ServerKey, "ServerA" } + }; + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(settings) + .Build(); + + var hostBuilder = new WebHostBuilder() + .UseConfiguration(config) + .UseSetting(WebHostDefaults.ServerKey, "ServerB") + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + + Assert.Equal("ServerB", host.ServerFactoryLocation); + } + + [Fact] + public void ConfigBasedSettingsConfigBasedOverride() + { + var settings = new Dictionary + { + { WebHostDefaults.ServerKey, "ServerA" } + }; + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(settings) + .Build(); + + var overrideSettings = new Dictionary + { + { WebHostDefaults.ServerKey, "ServerB" } + }; + + var overrideConfig = new ConfigurationBuilder() + .AddInMemoryCollection(overrideSettings) + .Build(); + + var hostBuilder = new WebHostBuilder() + .UseConfiguration(config) + .UseConfiguration(overrideConfig) + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + + Assert.Equal("ServerB", host.ServerFactoryLocation); + } + [Fact] public void UseEnvironmentIsNotOverriden() {