Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Halter <halter73@gmail.com>2021-09-29 01:30:35 +0300
committerStephen Halter <halter73@gmail.com>2021-09-29 01:48:26 +0300
commita5c042afbd07e3a1b24f691b8c623a776e7b42f4 (patch)
tree4dfda70a3158799ac48d266518eeb6064d7cecfb
parentf2a8e7a893a444ce4307859862ea7ba3e501b448 (diff)
Reverse configuration chaininghalter73/reverse-chain
-rw-r--r--src/DefaultBuilder/src/WebApplication.cs5
-rw-r--r--src/DefaultBuilder/src/WebApplicationBuilder.cs58
-rw-r--r--src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs100
3 files changed, 131 insertions, 32 deletions
diff --git a/src/DefaultBuilder/src/WebApplication.cs b/src/DefaultBuilder/src/WebApplication.cs
index d6c8ca5c60..9d0a837c83 100644
--- a/src/DefaultBuilder/src/WebApplication.cs
+++ b/src/DefaultBuilder/src/WebApplication.cs
@@ -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;
@@ -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;
diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs
index e7e6269d02..dfb153eb9b 100644
--- a/src/DefaultBuilder/src/WebApplicationBuilder.cs
+++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs
@@ -15,11 +15,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;
@@ -62,7 +63,6 @@ namespace Microsoft.AspNetCore.Builder
});
// 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 })
@@ -74,7 +74,6 @@ namespace Microsoft.AspNetCore.Builder
options.ApplyHostConfiguration(config);
});
-
Configuration = new();
// Collect the hosted services separately since we want those to run after the user's hosted services
@@ -100,7 +99,7 @@ namespace Microsoft.AspNetCore.Builder
Host = new ConfigureHostBuilder(hostContext, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
- Services.AddSingleton<IConfiguration>(Configuration);
+ Services.AddSingleton<IConfiguration>(_ => Configuration);
}
/// <summary>
@@ -148,14 +147,13 @@ namespace Microsoft.AspNetCore.Builder
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)
{
@@ -173,17 +171,6 @@ namespace Microsoft.AspNetCore.Builder
// 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);
}
@@ -205,6 +192,16 @@ namespace Microsoft.AspNetCore.Builder
// 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;
+
+ // 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 ((IConfigurationRoot)context.Configuration).Providers)
+ {
+ if (!ReferenceEquals(provider, chainedConfigSource.BuiltProvider))
+ {
+ ((IConfigurationBuilder)Configuration).Add(new ConfigurationProviderSource(provider));
+ }
+ }
});
// Run the other callbacks on the final host builder
@@ -212,11 +209,6 @@ namespace Microsoft.AspNetCore.Builder
_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;
@@ -301,6 +293,24 @@ namespace Microsoft.AspNetCore.Builder
public IServiceCollection Services { get; }
}
+ private 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;
+ }
+ }
+
private sealed class ConfigurationProviderSource : IConfigurationSource
{
private readonly IConfigurationProvider _configurationProvider;
diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs
index 595f5db980..d6b2326b6a 100644
--- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs
+++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs
@@ -23,7 +23,6 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
-using Xunit;
[assembly: HostingStartup(typeof(WebApplicationTests.TestHostingStartup))]
@@ -706,7 +705,7 @@ namespace Microsoft.AspNetCore.Tests
var app = builder.Build();
// These are different
- Assert.NotSame(app.Configuration, builder.Configuration);
+ Assert.Same(app.Configuration, builder.Configuration);
}
[Fact]
@@ -723,7 +722,28 @@ namespace Microsoft.AspNetCore.Tests
var app = builder.Build();
// These are different
- Assert.NotSame(app.Configuration, builder.Configuration);
+ Assert.Same(app.Configuration, builder.Configuration);
+ }
+
+ [Fact]
+ public void AddingMemoryStreamBackedConfigurationWorks()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ var jsonConfig = @"{ ""foo"": ""bar"" }";
+ using var ms = new MemoryStream();
+ using var sw = new StreamWriter(ms);
+ sw.WriteLine(jsonConfig);
+ sw.Flush();
+
+ ms.Position = 0;
+ builder.Configuration.AddJsonStream(ms);
+
+ Assert.Equal("bar", builder.Configuration["foo"]);
+
+ var app = builder.Build();
+
+ Assert.Equal("bar", app.Configuration["foo"]);
}
[Fact]
@@ -1496,6 +1516,22 @@ namespace Microsoft.AspNetCore.Tests
}
[Fact]
+ public void ConfigurationGetDebugViewWorks()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ builder.Configuration.AddInMemoryCollection(new Dictionary<string, string>
+ {
+ ["foo"] = "bar",
+ });
+
+ var app = builder.Build();
+
+ // Make sure we don't lose "MemoryConfigurationProvider" from GetDebugView() when wrapping the provider.
+ Assert.Contains("foo=bar (MemoryConfigurationProvider)", ((IConfigurationRoot)app.Configuration).GetDebugView());
+ }
+
+ [Fact]
public void ConfigurationCanBeReloaded()
{
var builder = WebApplication.CreateBuilder();
@@ -1524,23 +1560,77 @@ namespace Microsoft.AspNetCore.Tests
Assert.Equal(1, configSource.ProvidersBuilt);
}
+ [Fact]
+ public void ConfigurationProvidersAreLoadedOnceAfterBuild()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ var configSource = new RandomConfigurationSource();
+ ((IConfigurationBuilder)builder.Configuration).Sources.Add(configSource);
+
+ using var app = builder.Build();
+
+ Assert.Equal(1, configSource.ProvidersLoaded);
+ }
+
+ [Fact]
+ public void ConfigurationProvidersAreDisposedWithWebApplication()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ var configSource = new RandomConfigurationSource();
+ ((IConfigurationBuilder)builder.Configuration).Sources.Add(configSource);
+
+ {
+ using var app = builder.Build();
+
+ Assert.Equal(0, configSource.ProvidersDisposed);
+ }
+
+ Assert.Equal(1, configSource.ProvidersDisposed);
+ }
+
+ [Fact]
+ public void ConfigurationProviderTypesArePreserved()
+ {
+ var builder = WebApplication.CreateBuilder();
+
+ ((IConfigurationBuilder)builder.Configuration).Sources.Add(new RandomConfigurationSource());
+
+ var app = builder.Build();
+
+ Assert.Single(((IConfigurationRoot)app.Configuration).Providers.OfType<RandomConfigurationProvider>());
+ }
+
public class RandomConfigurationSource : IConfigurationSource
{
public int ProvidersBuilt { get; set; }
+ public int ProvidersLoaded { get; set; }
+ public int ProvidersDisposed { get; set; }
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
ProvidersBuilt++;
- return new RandomConfigurationProvider();
+ return new RandomConfigurationProvider(this);
}
}
- public class RandomConfigurationProvider : ConfigurationProvider
+ public class RandomConfigurationProvider : ConfigurationProvider, IDisposable
{
+ private readonly RandomConfigurationSource _source;
+
+ public RandomConfigurationProvider(RandomConfigurationSource source)
+ {
+ _source = source;
+ }
+
public override void Load()
{
+ _source.ProvidersLoaded++;
Data["Random"] = Guid.NewGuid().ToString();
}
+
+ public void Dispose() => _source.ProvidersDisposed++;
}
class ThrowingStartupFilter : IStartupFilter