Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix WebApplication to read environment specific logging configuration #33081

Merged
merged 2 commits into from
Jun 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/DefaultBuilder/DefaultBuilder.slnf
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
{
{
"solution": {
"path": "..\\..\\AspNetCore.sln",
"projects" : [
"projects": [
"src\\DefaultBuilder\\samples\\SampleApp\\DefaultBuilder.SampleApp.csproj",
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.Tests\\Microsoft.AspNetCore.Tests.csproj",
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.FunctionalTests\\Microsoft.AspNetCore.FunctionalTests.csproj",
"src\\DefaultBuilder\\src\\Microsoft.AspNetCore.csproj",
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.FunctionalTests\\Microsoft.AspNetCore.FunctionalTests.csproj",
"src\\DefaultBuilder\\test\\Microsoft.AspNetCore.Tests\\Microsoft.AspNetCore.Tests.csproj",
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
"src\\Hosting\\Server.IntegrationTesting\\src\\Microsoft.AspNetCore.Server.IntegrationTesting.csproj",
"src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj"
"src\\Http\\Features\\src\\Microsoft.Extensions.Features.csproj",
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
"src\\Middleware\\StaticFiles\\src\\Microsoft.AspNetCore.StaticFiles.csproj",
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj"
]
}
}
}
48 changes: 38 additions & 10 deletions src/DefaultBuilder/src/BootstrapHostBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,28 @@ namespace Microsoft.AspNetCore.Hosting
// This exists solely to bootstrap the configuration
internal class BootstrapHostBuilder : IHostBuilder
{
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
private readonly HostBuilderContext _context;
private readonly Configuration _configuration;
private readonly WebHostEnvironment _environment;

private readonly HostBuilderContext _hostContext;

private readonly List<Action<IConfigurationBuilder>> _configureHostActions = new();
private readonly List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppActions = new();

public BootstrapHostBuilder(Configuration configuration, WebHostEnvironment webHostEnvironment)
{
_configuration = configuration;
_environment = webHostEnvironment;
_context = new HostBuilderContext(Properties)

_hostContext = new HostBuilderContext(Properties)
{
Configuration = configuration,
HostingEnvironment = webHostEnvironment
};
}

public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();

public IHost Build()
{
// HostingHostBuilderExtensions.ConfigureDefaults should never call this.
Expand All @@ -37,9 +43,7 @@ public IHost Build()

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
configureDelegate(_context, _configuration);
_environment.ApplyConfigurationSettings(_configuration);
_configuration.ChangeBasePath(_environment.ContentRootPath);
_configureAppActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}

Expand All @@ -52,12 +56,15 @@ public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderCont

public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
configureDelegate(_configuration);
_environment.ApplyConfigurationSettings(_configuration);
_configuration.ChangeBasePath(_environment.ContentRootPath);
_configureHostActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}

public string? GetSetting(string key)
{
return _configuration[key];
}

public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
// HostingHostBuilderExtensions.ConfigureDefaults calls this via ConfigureLogging
Expand All @@ -67,7 +74,7 @@ public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollect

public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> 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;
}
Expand All @@ -78,5 +85,26 @@ public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilde
// during the initial config stage. It should be called again later on the ConfigureHostBuilder.
return this;
}

internal void RunConfigurationCallbacks()
{
foreach (var configureHostAction in _configureHostActions)
{
configureHostAction(_configuration);
}

// Configuration doesn't auto-update during the bootstrap phase to reduce I/O,
// but we do need to update between host and app configuration so the right environment is used.
_configuration.Update();
_environment.ApplyConfigurationSettings(_configuration);

foreach (var configureAppAction in _configureAppActions)
{
configureAppAction(_hostContext, _configuration);
}

_configuration.Update();
_environment.ApplyConfigurationSettings(_configuration);
}
}
}
166 changes: 93 additions & 73 deletions src/DefaultBuilder/src/Configuration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,126 +4,146 @@
using System;
using System.Collections;
using System.Collections.Generic;
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
{
/// <summary>
/// Configuration is mutable configuration object. It is both a configuration builder and an IConfigurationRoot.
/// Configuration is mutable configuration object. It is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
/// As sources are added, it updates its current view of configuration. Once Build is called, configuration is frozen.
/// </summary>
public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder
public sealed class Configuration : IConfigurationRoot, IConfigurationBuilder, IDisposable
{
private readonly ConfigurationBuilder _builder = new();
private IConfigurationRoot _configuration;
private readonly ConfigurationSources _sources;
private ConfigurationRoot _configurationRoot;

/// <summary>
/// Gets or sets a configuration value.
/// </summary>
/// <param name="key">The configuration key.</param>
/// <returns>The configuration value.</returns>
public string this[string key] { get => _configuration[key]; set => _configuration[key] = value; }
private ConfigurationReloadToken _changeToken = new();
private IDisposable? _changeTokenRegistration;

/// <summary>
/// Gets a configuration sub-section with the specified key.
/// Creates an empty mutable configuration object that is both an <see cref="IConfigurationBuilder"/> and an <see cref="IConfigurationRoot"/>.
/// </summary>
/// <param name="key">The key of the configuration section.</param>
/// <returns>The <see cref="IConfigurationSection"/>.</returns>
/// <remarks>
/// This method will never return <c>null</c>. If no matching sub-section is found with the specified key,
/// an empty <see cref="IConfigurationSection"/> will be returned.
/// </remarks>
public IConfigurationSection GetSection(string key)
public Configuration()
{
return _configuration.GetSection(key);
_sources = new ConfigurationSources(this);

// Make sure there's some default storage since there are no default providers.
this.AddInMemoryCollection();

Update();
}

/// <summary>
/// Gets the immediate descendant configuration sub-sections.
/// Automatically update the <see cref="IConfiguration"/> on <see cref="IConfigurationBuilder"/> changes.
/// If <see langword="false"/>, <see cref="Update()"/> will manually update the <see cref="IConfiguration"/>.
/// </summary>
/// <returns>The configuration sub-sections.</returns>
public IEnumerable<IConfigurationSection> GetChildren() => _configuration.GetChildren();
internal bool AutoUpdate { get; set; } = true;

/// <inheritdoc />
public string this[string key] { get => _configurationRoot[key]; set => _configurationRoot[key] = value; }

IDictionary<string, object> IConfigurationBuilder.Properties => _builder.Properties;
/// <inheritdoc />
public IConfigurationSection GetSection(string key) => new ConfigurationSection(this, key);

// TODO: Handle modifications to Sources and keep the configuration root in sync
IList<IConfigurationSource> IConfigurationBuilder.Sources => Sources;
/// <inheritdoc />
public IEnumerable<IConfigurationSection> GetChildren() => GetChildrenImplementation(null);

internal IList<IConfigurationSource> Sources { get; }
IDictionary<string, object> IConfigurationBuilder.Properties { get; } = new Dictionary<string, object>();

IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => _configuration.Providers;
IList<IConfigurationSource> IConfigurationBuilder.Sources => _sources;

IEnumerable<IConfigurationProvider> IConfigurationRoot.Providers => _configurationRoot.Providers;

/// <summary>
/// Creates a new <see cref="Configuration"/>.
/// Manually update the <see cref="IConfiguration"/> to reflect <see cref="IConfigurationBuilder"/> changes.
/// It is not necessary to call this if <see cref="AutoUpdate"/> is <see langword="true"/>.
/// </summary>
public Configuration()
[MemberNotNull(nameof(_configurationRoot))]
internal void Update()
{
_configuration = _builder.Build();
var newConfiguration = BuildConfigurationRoot();
var prevConfiguration = _configurationRoot;

_configurationRoot = newConfiguration;

var sources = new ConfigurationSources(_builder.Sources, UpdateConfigurationRoot);
_changeTokenRegistration?.Dispose();
(prevConfiguration as IDisposable)?.Dispose();

Sources = sources;
_changeTokenRegistration = ChangeToken.OnChange(() => newConfiguration.GetReloadToken(), RaiseChanged);
RaiseChanged();
}

internal void ChangeBasePath(string path)
/// <inheritdoc />
void IDisposable.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 NotifySourcesChanged()
{
var current = _configuration;
if (current is IDisposable disposable)
if (AutoUpdate)
{
disposable.Dispose();
Update();
}
_configuration = _builder.Build();
}

IConfigurationBuilder IConfigurationBuilder.Add(IConfigurationSource source)
{
Sources.Add(source);
return this;
}

IConfigurationRoot IConfigurationBuilder.Build()
private ConfigurationRoot BuildConfigurationRoot()
halter73 marked this conversation as resolved.
Show resolved Hide resolved
{
// No more modification is expected after this final build
UpdateConfigurationRoot();
return this;
var providers = new List<IConfigurationProvider>();
foreach (var source in _sources)
{
var provider = source.Build(this);
providers.Add(provider);
}
return new ConfigurationRoot(providers);
}

IChangeToken IConfiguration.GetReloadToken()
private void RaiseChanged()
{
// REVIEW: Is this correct?
return _configuration.GetReloadToken();
var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
previousToken.OnReload();
}

void IConfigurationRoot.Reload()
/// <summary>
/// Gets the immediate children sub-sections of configuration root based on key.
/// </summary>
/// <param name="path">Key of a section of which children to retrieve.</param>
/// <returns>Immediate children sub-sections of section specified by key.</returns>
private IEnumerable<IConfigurationSection> 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<string>(),
(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
private class ConfigurationSources : IList<IConfigurationSource>
{
private readonly IList<IConfigurationSource> _sources;
private readonly Action _sourcesModified;
private readonly List<IConfigurationSource> _sources = new();
private readonly Configuration _config;

public ConfigurationSources(IList<IConfigurationSource> sources, Action sourcesModified)
public ConfigurationSources(Configuration config)
{
_sources = sources;
_sourcesModified = sourcesModified;
_config = config;
}

public IConfigurationSource this[int index]
Expand All @@ -132,24 +152,24 @@ public IConfigurationSource this[int index]
set
{
_sources[index] = value;
_sourcesModified();
_config.NotifySourcesChanged();
}
}

public int Count => _sources.Count;

public bool IsReadOnly => _sources.IsReadOnly;
public bool IsReadOnly => false;

public void Add(IConfigurationSource item)
{
_sources.Add(item);
_sourcesModified();
_config.NotifySourcesChanged();
}

public void Clear()
{
_sources.Clear();
_sourcesModified();
_config.NotifySourcesChanged();
}

public bool Contains(IConfigurationSource item)
Expand All @@ -175,20 +195,20 @@ public int IndexOf(IConfigurationSource item)
public void Insert(int index, IConfigurationSource item)
{
_sources.Insert(index, item);
_sourcesModified();
_config.NotifySourcesChanged();
}

public bool Remove(IConfigurationSource item)
{
var removed = _sources.Remove(item);
_sourcesModified();
_config.NotifySourcesChanged();
return removed;
}

public void RemoveAt(int index)
{
_sources.RemoveAt(index);
_sourcesModified();
_config.NotifySourcesChanged();
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
Loading