Ordering sensitive configuration #582

This commit is contained in:
John Luo 2016-01-25 17:32:22 -08:00
parent 3ea44c6f75
commit ae47bb21a6
7 changed files with 156 additions and 53 deletions

View File

@ -2,10 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Hosting namespace Microsoft.AspNetCore.Hosting
@ -19,20 +17,6 @@ namespace Microsoft.AspNetCore.Hosting
/// Builds an <see cref="IWebHost"/> which hosts a web application. /// Builds an <see cref="IWebHost"/> which hosts a web application.
/// </summary> /// </summary>
IWebHost Build(); IWebHost Build();
/// <summary>
/// Gets the raw settings to be used by the web host. Values specified here will override
/// the configuration set by <see cref="UseConfiguration(IConfiguration)"/>.
/// </summary>
IDictionary<string, string> Settings { get; }
/// <summary>
/// Specify the <see cref="IConfiguration"/> to be used by the web host. If no configuration is
/// provided to the builder, the default configuration will be used.
/// </summary>
/// <param name="configuration">The <see cref="IConfiguration"/> to be used.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder UseConfiguration(IConfiguration configuration);
/// <summary> /// <summary>
/// Specify the <see cref="IServerFactory"/> to be used by the web host. /// Specify the <see cref="IServerFactory"/> to be used by the web host.
@ -63,11 +47,18 @@ namespace Microsoft.AspNetCore.Hosting
IWebHostBuilder Configure(Action<IApplicationBuilder> configureApplication); IWebHostBuilder Configure(Action<IApplicationBuilder> configureApplication);
/// <summary> /// <summary>
/// Add or replace a setting in <see cref="Settings"/>. /// Add or replace a setting in the configuration.
/// </summary> /// </summary>
/// <param name="key">The key of the setting to add or replace.</param> /// <param name="key">The key of the setting to add or replace.</param>
/// <param name="value">The value of the setting to add or replace.</param> /// <param name="value">The value of the setting to add or replace.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder UseSetting(string key, string value); IWebHostBuilder UseSetting(string key, string value);
/// <summary>
/// Get the setting value from the configuration.
/// </summary>
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
} }
} }

View File

@ -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<KeyValuePair<string, string>> GetFlattenedSettings(this IConfiguration configuration)
{
var stack = new Stack<IConfiguration>();
stack.Push(configuration);
while (stack.Count > 0)
{
var config = stack.Pop();
var section = config as IConfigurationSection;
if (section != null)
{
yield return new KeyValuePair<string, string>(section.Path, section.Value);
}
foreach (var child in config.GetChildren())
{
stack.Push(child);
}
}
}
}
}

View File

@ -4,7 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Hosting namespace Microsoft.AspNetCore.Hosting.Internal
{ {
public class WebHostConfiguration public class WebHostConfiguration
{ {

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
@ -28,7 +27,7 @@ namespace Microsoft.AspNetCore.Hosting
private readonly IHostingEnvironment _hostingEnvironment; private readonly IHostingEnvironment _hostingEnvironment;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private IConfiguration _config; private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build();
private WebHostOptions _options; private WebHostOptions _options;
private Action<IServiceCollection> _configureServices; private Action<IServiceCollection> _configureServices;
@ -40,26 +39,12 @@ namespace Microsoft.AspNetCore.Hosting
// Only one of these should be set // Only one of these should be set
private IServerFactory _serverFactory; private IServerFactory _serverFactory;
private IDictionary<string, string> _settings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
public WebHostBuilder() public WebHostBuilder()
{ {
_hostingEnvironment = new HostingEnvironment(); _hostingEnvironment = new HostingEnvironment();
_loggerFactory = new LoggerFactory(); _loggerFactory = new LoggerFactory();
} }
/// <summary>
/// Gets the raw settings to be used by the web host. Values specified here will override
/// the configuration set by <see cref="UseConfiguration(IConfiguration)"/>.
/// </summary>
public IDictionary<string, string> Settings
{
get
{
return _settings;
}
}
/// <summary> /// <summary>
/// Add or replace a setting in <see cref="Settings"/>. /// Add or replace a setting in <see cref="Settings"/>.
/// </summary> /// </summary>
@ -68,20 +53,18 @@ namespace Microsoft.AspNetCore.Hosting
/// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder UseSetting(string key, string value) public IWebHostBuilder UseSetting(string key, string value)
{ {
_settings[key] = value; _config[key] = value;
return this; return this;
} }
/// <summary> /// <summary>
/// Specify the <see cref="IConfiguration"/> to be used by the web host. If no configuration is /// Get the setting value from the configuration.
/// provided to the builder, the default configuration will be used.
/// </summary> /// </summary>
/// <param name="configuration">The <see cref="IConfiguration"/> to be used.</param> /// <param name="key">The key of the setting to look up.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns> /// <returns>The value the setting currently contains.</returns>
public IWebHostBuilder UseConfiguration(IConfiguration configuration) public string GetSetting(string key)
{ {
_config = configuration; return _config[key];
return this;
} }
/// <summary> /// <summary>
@ -187,15 +170,6 @@ namespace Microsoft.AspNetCore.Hosting
private IServiceCollection BuildHostingServices() 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); _options = new WebHostOptions(_config);
var services = new ServiceCollection(); var services = new ServiceCollection();

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -21,6 +22,16 @@ namespace Microsoft.AspNetCore.Hosting
return builder.UseConfiguration(WebHostConfiguration.GetDefault(args)); 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) public static IWebHostBuilder UseCaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupError)
{ {
return hostBuilder.UseSetting(WebHostDefaults.CaptureStartupErrorsKey, captureStartupError ? "true" : "false"); return hostBuilder.UseSetting(WebHostDefaults.CaptureStartupErrorsKey, captureStartupError ? "true" : "false");

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.TestHost
public TestServer(IWebHostBuilder builder) public TestServer(IWebHostBuilder builder)
{ {
if (!builder.Settings.ContainsKey(WebHostDefaults.CaptureStartupErrorsKey)) if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.CaptureStartupErrorsKey)))
{ {
builder.UseCaptureStartupErrors(false); builder.UseCaptureStartupErrors(false);
} }

View File

@ -124,9 +124,10 @@ namespace Microsoft.AspNetCore.Hosting
} }
[Fact] [Fact]
public void CaptureStartupErrorsByDefault() public void DefaultConfigurationCapturesStartupErrors()
{ {
var hostBuilder = new WebHostBuilder() var hostBuilder = new WebHostBuilder()
.UseDefaultConfiguration()
.UseServer(new TestServer()) .UseServer(new TestServer())
.UseStartup<StartupBoom>(); .UseStartup<StartupBoom>();
@ -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); 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<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
}
[Fact]
public void CodeBasedSettingsConfigBasedOverride()
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerB" }
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(settings)
.Build();
var hostBuilder = new WebHostBuilder()
.UseSetting(WebHostDefaults.ServerKey, "ServerA")
.UseConfiguration(config)
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
}
[Fact]
public void ConfigBasedSettingsCodeBasedOverride()
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerA" }
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(settings)
.Build();
var hostBuilder = new WebHostBuilder()
.UseConfiguration(config)
.UseSetting(WebHostDefaults.ServerKey, "ServerB")
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
}
[Fact]
public void ConfigBasedSettingsConfigBasedOverride()
{
var settings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerA" }
};
var config = new ConfigurationBuilder()
.AddInMemoryCollection(settings)
.Build();
var overrideSettings = new Dictionary<string, string>
{
{ WebHostDefaults.ServerKey, "ServerB" }
};
var overrideConfig = new ConfigurationBuilder()
.AddInMemoryCollection(overrideSettings)
.Build();
var hostBuilder = new WebHostBuilder()
.UseConfiguration(config)
.UseConfiguration(overrideConfig)
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal("ServerB", host.ServerFactoryLocation);
}
[Fact] [Fact]
public void UseEnvironmentIsNotOverriden() public void UseEnvironmentIsNotOverriden()
{ {