Skip to content
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
99 changes: 99 additions & 0 deletions src/DefaultBuilder/src/ConfigurationProviderSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Primitives;

namespace Microsoft.AspNetCore.Builder
{
internal sealed class ConfigurationProviderSource : IConfigurationSource
{
private readonly IConfigurationProvider _configurationProvider;

public ConfigurationProviderSource(IConfigurationProvider configurationProvider)
{
_configurationProvider = configurationProvider;
}

public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new IgnoreFirstLoadConfigurationProvider(_configurationProvider);
}

// These providers have already been loaded, so no need to reload initially.
// Otherwise, providers that cannot be reloaded like StreamConfigurationProviders will fail.
private sealed class IgnoreFirstLoadConfigurationProvider : IConfigurationProvider, IEnumerable<IConfigurationProvider>, IDisposable
{
private readonly IConfigurationProvider _provider;

private bool _hasIgnoredFirstLoad;

public IgnoreFirstLoadConfigurationProvider(IConfigurationProvider provider)
{
_provider = provider;
}

public IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
{
return _provider.GetChildKeys(earlierKeys, parentPath);
}

public IChangeToken GetReloadToken()
{
return _provider.GetReloadToken();
}

public void Load()
{
if (!_hasIgnoredFirstLoad)
{
_hasIgnoredFirstLoad = true;
return;
}

_provider.Load();
}

public void Set(string key, string value)
{
_provider.Set(key, value);
}

public bool TryGet(string key, out string value)
{
return _provider.TryGet(key, out value);
}

// Provide access to the original IConfigurationProvider via a single-element IEnumerable to code that goes out of its way to look for it.
public IEnumerator<IConfigurationProvider> GetEnumerator() => GetUnwrappedEnumerable().GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetUnwrappedEnumerable().GetEnumerator();

public override bool Equals(object? obj)
{
return _provider.Equals(obj);
}

public override int GetHashCode()
{
return _provider.GetHashCode();
}

public override string? ToString()
{
return _provider.ToString();
}

public void Dispose()
{
(_provider as IDisposable)?.Dispose();
}

private IEnumerable<IConfigurationProvider> GetUnwrappedEnumerable()
{
yield return _provider;
}
}
}
}
25 changes: 25 additions & 0 deletions src/DefaultBuilder/src/TrackingChainedConfigurationSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Configuration;

namespace Microsoft.AspNetCore.Builder
{
internal sealed class TrackingChainedConfigurationSource : IConfigurationSource
{
private readonly ChainedConfigurationSource _chainedConfigurationSource = new();

public TrackingChainedConfigurationSource(ConfigurationManager configManager)
{
_chainedConfigurationSource.Configuration = configManager;
}

public IConfigurationProvider? BuiltProvider { get; set; }

public IConfigurationProvider Build(IConfigurationBuilder builder)
{
BuiltProvider = _chainedConfigurationSource.Build(builder);
return BuiltProvider;
}
}
}
5 changes: 2 additions & 3 deletions src/DefaultBuilder/src/WebApplication.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
Expand All @@ -20,11 +19,11 @@ namespace Microsoft.AspNetCore.Builder
/// </summary>
public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";

private readonly IHost _host;
private readonly List<EndpointDataSource> _dataSources = new();

internal static string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";

internal WebApplication(IHost host)
{
_host = host;
Expand Down
69 changes: 30 additions & 39 deletions src/DefaultBuilder/src/WebApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -15,11 +16,12 @@ namespace Microsoft.AspNetCore.Builder
/// </summary>
public sealed class WebApplicationBuilder
{
private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder";

private readonly HostBuilder _hostBuilder = new();
private readonly BootstrapHostBuilder _bootstrapHostBuilder;
private readonly WebApplicationServiceCollection _services = new();
private readonly List<KeyValuePair<string, string>> _hostConfigurationValues;
private const string EndpointRouteBuilderKey = "__EndpointRouteBuilder";

private WebApplication? _builtApplication;

Expand Down Expand Up @@ -62,7 +64,6 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
});

// Apply the args to host configuration last since ConfigureWebHostDefaults overrides a host specific setting (the application name).

_bootstrapHostBuilder.ConfigureHostConfiguration(config =>
{
if (args is { Length: > 0 })
Expand All @@ -74,7 +75,6 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
options.ApplyHostConfiguration(config);
});


Configuration = new();

// Collect the hosted services separately since we want those to run after the user's hosted services
Expand All @@ -100,7 +100,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
Host = new ConfigureHostBuilder(hostContext, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);

Services.AddSingleton<IConfiguration>(Configuration);
Services.AddSingleton<IConfiguration>(_ => Configuration);
}

/// <summary>
Expand Down Expand Up @@ -148,14 +148,13 @@ public WebApplication Build()
builder.AddInMemoryCollection(_hostConfigurationValues);
});

var chainedConfigSource = new TrackingChainedConfigurationSource(Configuration);

// Wire up the application configuration by copying the already built configuration providers over to final configuration builder.
// We wrap the existing provider in a configuration source to avoid re-bulding the already added configuration sources.
_hostBuilder.ConfigureAppConfiguration(builder =>
{
foreach (var provider in ((IConfigurationRoot)Configuration).Providers)
{
builder.Sources.Add(new ConfigurationProviderSource(provider));
}
builder.Add(chainedConfigSource);

foreach (var (key, value) in ((IConfigurationBuilder)Configuration).Properties)
{
Expand All @@ -173,17 +172,6 @@ public WebApplication Build()
// we called ConfigureWebHostDefaults on both the _deferredHostBuilder and _hostBuilder.
foreach (var s in _services)
{
// Skip the configuration manager instance we added earlier
// we're already going to wire it up to this new configuration source
// after we've built the application. There's a chance the user manually added
// this as well but we still need to remove it from the final configuration
// to avoid cycles in the configuration graph
if (s.ServiceType == typeof(IConfiguration) &&
s.ImplementationInstance == Configuration)
{
continue;
}

services.Add(s);
}

Expand All @@ -205,21 +193,39 @@ public WebApplication Build()
// Drop the reference to the existing collection and set the inner collection
// to the new one. This allows code that has references to the service collection to still function.
_services.InnerCollection = services;

var hostBuilderProviders = ((IConfigurationRoot)context.Configuration).Providers;

if (!hostBuilderProviders.Contains(chainedConfigSource.BuiltProvider))
{
// Something removed the _hostBuilder's TrackingChainedConfigurationSource pointing back to the ConfigurationManager.
// This is likely a test using WebApplicationFactory. Replicate the effect by clearing the ConfingurationManager sources.
((IConfigurationBuilder)Configuration).Sources.Clear();
}

// Make builder.Configuration match the final configuration. To do that, we add the additional
// providers in the inner _hostBuilders's Configuration to the ConfigurationManager.
foreach (var provider in hostBuilderProviders)
{
if (!ReferenceEquals(provider, chainedConfigSource.BuiltProvider))
{
((IConfigurationBuilder)Configuration).Add(new ConfigurationProviderSource(provider));
}
}
});

// Run the other callbacks on the final host builder
Host.RunDeferredCallbacks(_hostBuilder);

_builtApplication = new WebApplication(_hostBuilder.Build());

// Make builder.Configuration match the final configuration. To do that
// we clear the sources and add the built configuration as a source
((IConfigurationBuilder)Configuration).Sources.Clear();
Configuration.AddConfiguration(_builtApplication.Configuration);

// Mark the service collection as read-only to prevent future modifications
_services.IsReadOnly = true;

// Resolve both the _hostBuilder's Configuration and builder.Configuration to mark both as resolved within the
// service provider ensuring both will be properly disposed with the provider.
_ = _builtApplication.Services.GetService<IEnumerable<IConfiguration>>();

return _builtApplication;
}

Expand Down Expand Up @@ -300,20 +306,5 @@ public LoggingBuilder(IServiceCollection services)

public IServiceCollection Services { get; }
}

private sealed class ConfigurationProviderSource : IConfigurationSource
{
private readonly IConfigurationProvider _configurationProvider;

public ConfigurationProviderSource(IConfigurationProvider configurationProvider)
{
_configurationProvider = configurationProvider;
}

public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return _configurationProvider;
}
}
}
}
Loading