From 83c4bd889af807b636edee7fd2aec786aec8ea4d Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 18 Nov 2022 18:14:17 -0600 Subject: [PATCH 1/7] Allow minimal host to be created without default HostBuilder behavior This adds a new Hosting API to reduce startup and app size, and ensures the default behavior is NativeAOT compatible. Fix #32485 --- .../src/PublicAPI.Unshipped.txt | 3 + src/DefaultBuilder/src/WebApplication.cs | 23 + .../src/WebApplicationBuilder.cs | 128 +++- src/DefaultBuilder/src/WebHost.cs | 14 +- .../WebApplicationTests.cs | 656 +++++++++++------- .../src/GenericHost/SlimWebHostBuilder.cs | 186 +++++ .../GenericHostWebHostBuilderExtensions.cs | 31 +- .../Hosting/src/PublicAPI.Unshipped.txt | 1 + 8 files changed, 774 insertions(+), 268 deletions(-) create mode 100644 src/Hosting/Hosting/src/GenericHost/SlimWebHostBuilder.cs diff --git a/src/DefaultBuilder/src/PublicAPI.Unshipped.txt b/src/DefaultBuilder/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..c4dde0ab6378 100644 --- a/src/DefaultBuilder/src/PublicAPI.Unshipped.txt +++ b/src/DefaultBuilder/src/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder() -> Microsoft.AspNetCore.Builder.WebApplicationBuilder! +static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(Microsoft.AspNetCore.Builder.WebApplicationOptions! options) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder! +static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(string![]! args) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder! diff --git a/src/DefaultBuilder/src/WebApplication.cs b/src/DefaultBuilder/src/WebApplication.cs index 1a9db8c6df1e..6056b3ff4d02 100644 --- a/src/DefaultBuilder/src/WebApplication.cs +++ b/src/DefaultBuilder/src/WebApplication.cs @@ -98,6 +98,13 @@ public static WebApplication Create(string[]? args = null) => public static WebApplicationBuilder CreateBuilder() => new(new()); + /// + /// Initializes a new instance of the class with minimal defaults. + /// + /// The . + public static WebApplicationBuilder CreateSlimBuilder() => + new(new(), slim: true); + /// /// Initializes a new instance of the class with preconfigured defaults. /// @@ -106,6 +113,14 @@ public static WebApplicationBuilder CreateBuilder() => public static WebApplicationBuilder CreateBuilder(string[] args) => new(new() { Args = args }); + /// + /// Initializes a new instance of the class with minimal defaults. + /// + /// Command line arguments + /// The . + public static WebApplicationBuilder CreateSlimBuilder(string[] args) => + new(new() { Args = args }, slim: true); + /// /// Initializes a new instance of the class with preconfigured defaults. /// @@ -114,6 +129,14 @@ public static WebApplicationBuilder CreateBuilder(string[] args) => public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) => new(options); + /// + /// Initializes a new instance of the class with minimal defaults. + /// + /// The to configure the . + /// The . + public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) => + new(options, slim: true); + /// /// Start the application. /// diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 3da1ac4e83a7..5792ed94169a 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -86,6 +86,70 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action? configureDefaults = null) + { + Debug.Assert(slim, "should only be called with slim: true"); + + var configuration = new ConfigurationManager(); + + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings + { + Args = options.Args, + ApplicationName = options.ApplicationName, + EnvironmentName = options.EnvironmentName, + ContentRootPath = options.ContentRootPath, + Configuration = configuration, + }); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + + // Set WebRootPath if necessary + if (options.WebRootPath is not null) + { + Configuration.AddInMemoryCollection(new[] + { + new KeyValuePair(WebHostDefaults.WebRootKey, options.WebRootPath), + }); + } + + // Run methods to configure web host defaults early to populate services + var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder); + + // This is for testing purposes + configureDefaults?.Invoke(bootstrapHostBuilder); + + bootstrapHostBuilder.ConfigureSlimWebHost( + webHostBuilder => + { + AspNetCore.WebHost.UseKestrel(webHostBuilder); + + webHostBuilder.Configure(ConfigureEmptyApplication); + + webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? ""); + webHostBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, Configuration[WebHostDefaults.PreventHostingStartupKey]); + webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, Configuration[WebHostDefaults.HostingStartupAssembliesKey]); + webHostBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, Configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]); + }, + options => + { + // We've already applied "ASPNETCORE_" environment variables to hosting config + options.SuppressEnvironmentConfiguration = true; + }); + + // This applies the config from ConfigureWebHostDefaults + // Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build(); + _genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks(); + + // Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then + // grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection. + var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)]; + Environment = webHostContext.HostingEnvironment; + + Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services); + WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services); + } + /// /// Provides information about the web hosting environment an application is running. /// @@ -133,6 +197,46 @@ public WebApplication Build() } private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) + { + ConfigureApplicationCore( + context, + app, + processAuthMiddlewares: () => + { + Debug.Assert(_builtApplication is not null); + + // Process authorization and authentication middlewares independently to avoid + // registering middlewares for services that do not exist + var serviceProviderIsService = _builtApplication.Services.GetService(); + if (serviceProviderIsService?.IsService(typeof(IAuthenticationSchemeProvider)) is true) + { + // Don't add more than one instance of the middleware + if (!_builtApplication.Properties.ContainsKey(AuthenticationMiddlewareSetKey)) + { + // The Use invocations will set the property on the outer pipeline, + // but we want to set it on the inner pipeline as well. + _builtApplication.Properties[AuthenticationMiddlewareSetKey] = true; + app.UseAuthentication(); + } + } + + if (serviceProviderIsService?.IsService(typeof(IAuthorizationHandlerProvider)) is true) + { + if (!_builtApplication.Properties.ContainsKey(AuthorizationMiddlewareSetKey)) + { + _builtApplication.Properties[AuthorizationMiddlewareSetKey] = true; + app.UseAuthorization(); + } + } + }); + } + + private void ConfigureEmptyApplication(WebHostBuilderContext context, IApplicationBuilder app) + { + ConfigureApplicationCore(context, app, processAuthMiddlewares: null); + } + + private void ConfigureApplicationCore(WebHostBuilderContext context, IApplicationBuilder app, Action? processAuthMiddlewares) { Debug.Assert(_builtApplication is not null); @@ -173,29 +277,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui } } - // Process authorization and authentication middlewares independently to avoid - // registering middlewares for services that do not exist - var serviceProviderIsService = _builtApplication.Services.GetService(); - if (serviceProviderIsService?.IsService(typeof(IAuthenticationSchemeProvider)) is true) - { - // Don't add more than one instance of the middleware - if (!_builtApplication.Properties.ContainsKey(AuthenticationMiddlewareSetKey)) - { - // The Use invocations will set the property on the outer pipeline, - // but we want to set it on the inner pipeline as well. - _builtApplication.Properties[AuthenticationMiddlewareSetKey] = true; - app.UseAuthentication(); - } - } - - if (serviceProviderIsService?.IsService(typeof(IAuthorizationHandlerProvider)) is true) - { - if (!_builtApplication.Properties.ContainsKey(AuthorizationMiddlewareSetKey)) - { - _builtApplication.Properties[AuthorizationMiddlewareSetKey] = true; - app.UseAuthorization(); - } - } + processAuthMiddlewares?.Invoke(); // Wire the source pipeline to run in the destination pipeline app.Use(next => diff --git a/src/DefaultBuilder/src/WebHost.cs b/src/DefaultBuilder/src/WebHost.cs index 156efd0e326e..4a49c3ef8e3c 100644 --- a/src/DefaultBuilder/src/WebHost.cs +++ b/src/DefaultBuilder/src/WebHost.cs @@ -222,6 +222,16 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder) StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration); } }); + + UseKestrel(builder); + + builder + .UseIIS() + .UseIISIntegration(); + } + + internal static void UseKestrel(IWebHostBuilder builder) + { builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true); @@ -248,9 +258,7 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder) services.AddTransient, ForwardedHeadersOptionsSetup>(); services.AddRouting(); - }) - .UseIIS() - .UseIISIntegration(); + }); } /// diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs index ea386a85e0b2..9d94502ca80f 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs @@ -36,20 +36,77 @@ namespace Microsoft.AspNetCore.Tests; public class WebApplicationTests { - [Fact] - public async Task WebApplicationBuilder_New() + public delegate WebApplicationBuilder CreateBuilderFunc(); + public delegate WebApplicationBuilder CreateBuilderArgsFunc(string[] args); + public delegate WebApplicationBuilder CreateBuilderOptionsFunc(WebApplicationOptions options); + public delegate WebApplicationBuilder WebApplicationBuilderConstructorFunc(WebApplicationOptions options, Action configureDefaults); + + private static WebApplicationBuilder CreateBuilder() => WebApplication.CreateBuilder(); + private static WebApplicationBuilder CreateSlimBuilder() => WebApplication.CreateSlimBuilder(); + + public static IEnumerable CreateBuilderFuncs + { + get + { + yield return new[] { (CreateBuilderFunc)CreateBuilder }; + yield return new[] { (CreateBuilderFunc)CreateSlimBuilder }; + } + } + + private static WebApplicationBuilder CreateBuilderArgs(string[] args) => WebApplication.CreateBuilder(args); + private static WebApplicationBuilder CreateSlimBuilderArgs(string[] args) => WebApplication.CreateSlimBuilder(args); + + public static IEnumerable CreateBuilderArgsFuncs + { + get + { + yield return new[] { (CreateBuilderArgsFunc)CreateBuilderArgs }; + yield return new[] { (CreateBuilderArgsFunc)CreateSlimBuilderArgs }; + } + } + + private static WebApplicationBuilder CreateBuilderOptions(WebApplicationOptions options) => WebApplication.CreateBuilder(options); + private static WebApplicationBuilder CreateSlimBuilderOptions(WebApplicationOptions options) => WebApplication.CreateSlimBuilder(options); + + public static IEnumerable CreateBuilderOptionsFuncs + { + get + { + yield return new[] { (CreateBuilderOptionsFunc)CreateBuilderOptions }; + yield return new[] { (CreateBuilderOptionsFunc)CreateSlimBuilderOptions }; + } + } + + private static WebApplicationBuilder WebApplicationBuilderConstructor(WebApplicationOptions options, Action configureDefaults) + => new WebApplicationBuilder(options, configureDefaults); + private static WebApplicationBuilder WebApplicationSlimBuilderConstructor(WebApplicationOptions options, Action configureDefaults) + => new WebApplicationBuilder(options, slim: true, configureDefaults); + + public static IEnumerable WebApplicationBuilderConstructorFuncs + { + get + { + yield return new[] { (WebApplicationBuilderConstructorFunc)WebApplicationBuilderConstructor }; + yield return new[] { (WebApplicationBuilderConstructorFunc)WebApplicationSlimBuilderConstructor }; + } + } + + [Theory] + [MemberData(nameof(CreateBuilderArgsFuncs))] + public async Task WebApplicationBuilder_New(CreateBuilderArgsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new string[] { "--urls", "http://localhost:5001" }); + var builder = createBuilder(new string[] { "--urls", "http://localhost:5001" }); await using var app = builder.Build(); var newApp = (app as IApplicationBuilder).New(); Assert.NotNull(newApp.ServerFeatures); } - [Fact] - public async Task WebApplicationBuilderConfiguration_IncludesCommandLineArguments() + [Theory] + [MemberData(nameof(CreateBuilderArgsFuncs))] + public async Task WebApplicationBuilderConfiguration_IncludesCommandLineArguments(CreateBuilderArgsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new string[] { "--urls", "http://localhost:5001" }); + var builder = createBuilder(new string[] { "--urls", "http://localhost:5001" }); Assert.Equal("http://localhost:5001", builder.Configuration["urls"]); var urls = new List(); @@ -68,10 +125,11 @@ public async Task WebApplicationBuilderConfiguration_IncludesCommandLineArgument Assert.Equal("http://localhost:5001", url); } - [Fact] - public async Task WebApplicationRunAsync_UsesDefaultUrls() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationRunAsync_UsesDefaultUrls(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var urls = new List(); var server = new MockAddressesServer(urls); builder.Services.AddSingleton(server); @@ -86,10 +144,11 @@ public async Task WebApplicationRunAsync_UsesDefaultUrls() Assert.Equal("https://localhost:5001", urls[1]); } - [Fact] - public async Task WebApplicationRunUrls_UpdatesIServerAddressesFeature() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationRunUrls_UpdatesIServerAddressesFeature(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var urls = new List(); var server = new MockAddressesServer(urls); builder.Services.AddSingleton(server); @@ -104,10 +163,11 @@ public async Task WebApplicationRunUrls_UpdatesIServerAddressesFeature() await runTask; } - [Fact] - public async Task WebApplicationUrls_UpdatesIServerAddressesFeature() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationUrls_UpdatesIServerAddressesFeature(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var urls = new List(); var server = new MockAddressesServer(urls); builder.Services.AddSingleton(server); @@ -123,10 +183,11 @@ public async Task WebApplicationUrls_UpdatesIServerAddressesFeature() Assert.Equal("https://localhost:5003", urls[1]); } - [Fact] - public async Task WebApplicationRunUrls_OverridesIServerAddressesFeature() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationRunUrls_OverridesIServerAddressesFeature(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var urls = new List(); var server = new MockAddressesServer(urls); builder.Services.AddSingleton(server); @@ -144,20 +205,22 @@ public async Task WebApplicationRunUrls_OverridesIServerAddressesFeature() await runTask; } - [Fact] - public async Task WebApplicationUrls_ThrowsInvalidOperationExceptionIfThereIsNoIServerAddressesFeature() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationUrls_ThrowsInvalidOperationExceptionIfThereIsNoIServerAddressesFeature(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Services.AddSingleton(new MockAddressesServer()); await using var app = builder.Build(); Assert.Throws(() => app.Urls); } - [Fact] - public async Task HostedServicesRunBeforeTheServerStarts() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task HostedServicesRunBeforeTheServerStarts(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var startOrder = new List(); var server = new MockServer(startOrder); var hostedService = new HostedService(startOrder); @@ -215,42 +278,47 @@ public Task StopAsync(CancellationToken cancellationToken) } } - [Fact] - public async Task WebApplicationRunUrls_ThrowsInvalidOperationExceptionIfThereIsNoIServerAddressesFeature() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationRunUrls_ThrowsInvalidOperationExceptionIfThereIsNoIServerAddressesFeature(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Services.AddSingleton(new MockAddressesServer()); await using var app = builder.Build(); await Assert.ThrowsAsync(() => app.RunAsync("http://localhost:5001")); } - [Fact] - public async Task WebApplicationRunUrls_ThrowsInvalidOperationExceptionIfServerAddressesFeatureIsReadOnly() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationRunUrls_ThrowsInvalidOperationExceptionIfServerAddressesFeatureIsReadOnly(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Services.AddSingleton(new MockAddressesServer(new List().AsReadOnly())); await using var app = builder.Build(); await Assert.ThrowsAsync(() => app.RunAsync("http://localhost:5001")); } - [Fact] - public void WebApplicationBuilderHost_ThrowsWhenBuiltDirectly() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderHost_ThrowsWhenBuiltDirectly(CreateBuilderFunc createBuilder) { - Assert.Throws(() => ((IHostBuilder)WebApplication.CreateBuilder().Host).Build()); + Assert.Throws(() => ((IHostBuilder)createBuilder().Host).Build()); } - [Fact] - public void WebApplicationBuilderWebHost_ThrowsWhenBuiltDirectly() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderWebHost_ThrowsWhenBuiltDirectly(CreateBuilderFunc createBuilder) { - Assert.Throws(() => ((IWebHostBuilder)WebApplication.CreateBuilder().WebHost).Build()); + Assert.Throws(() => ((IWebHostBuilder)createBuilder().WebHost).Build()); } - [Fact] - public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModified() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModified(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var webRoot = Path.Combine(contentRoot, "wwwroot"); @@ -266,10 +334,11 @@ public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModifie Assert.Throws(() => builder.WebHost.UseContentRoot(contentRoot)); } - [Fact] - public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModifiedViaConfigureAppConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModifiedViaConfigureAppConfiguration(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var webRoot = Path.Combine(contentRoot, "wwwroot"); @@ -324,13 +393,14 @@ public void WebApplicationBuilderWebHostSettingsThatAffectTheHostCannotBeModifie })); } - [Fact] - public void SettingContentRootToSameCanonicalValueWorks() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public void SettingContentRootToSameCanonicalValueWorks(CreateBuilderOptionsFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(contentRoot); - var builder = WebApplication.CreateBuilder(new WebApplicationOptions + var builder = createBuilder(new WebApplicationOptions { ContentRootPath = contentRoot }); @@ -344,13 +414,21 @@ public void SettingContentRootToSameCanonicalValueWorks() builder.WebHost.UseContentRoot(contentRoot.ToLowerInvariant()); } + public static IEnumerable CanHandleVariousWebRootPathsData + { + get + { + foreach (string webRoot in new[] { "wwwroot2", "./wwwroot2", "./bar/../wwwroot2", "foo/../wwwroot2", "wwwroot2/." }) + { + yield return new object[] { webRoot, (CreateBuilderOptionsFunc)CreateBuilderOptions }; + yield return new object[] { webRoot, (CreateBuilderOptionsFunc)CreateSlimBuilderOptions }; + } + } + } + [Theory] - [InlineData("wwwroot2")] - [InlineData("./wwwroot2")] - [InlineData("./bar/../wwwroot2")] - [InlineData("foo/../wwwroot2")] - [InlineData("wwwroot2/.")] - public void WebApplicationBuilder_CanHandleVariousWebRootPaths(string webRoot) + [MemberData(nameof(CanHandleVariousWebRootPathsData))] + public void WebApplicationBuilder_CanHandleVariousWebRootPaths(string webRoot, CreateBuilderOptionsFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -364,7 +442,7 @@ public void WebApplicationBuilder_CanHandleVariousWebRootPaths(string webRoot) WebRootPath = "wwwroot2" }; - var builder = new WebApplicationBuilder(options); + var builder = createBuilder(options); Assert.Equal(contentRoot, builder.Environment.ContentRootPath); Assert.Equal(fullWebRootPath, builder.Environment.WebRootPath); @@ -377,8 +455,9 @@ public void WebApplicationBuilder_CanHandleVariousWebRootPaths(string webRoot) } } - [Fact] - public void WebApplicationBuilder_CanOverrideWithFullWebRootPaths() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public void WebApplicationBuilder_CanOverrideWithFullWebRootPaths(CreateBuilderOptionsFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -392,7 +471,7 @@ public void WebApplicationBuilder_CanOverrideWithFullWebRootPaths() ContentRootPath = contentRoot, }; - var builder = new WebApplicationBuilder(options); + var builder = createBuilder(options); Assert.Equal(contentRoot, builder.Environment.ContentRootPath); Assert.Equal(fullWebRootPath, builder.Environment.WebRootPath); @@ -405,13 +484,21 @@ public void WebApplicationBuilder_CanOverrideWithFullWebRootPaths() } } + public static IEnumerable CanHandleVariousWebRootPaths_OverrideDefaultPathData + { + get + { + foreach (string webRoot in new[] { "wwwroot", "./wwwroot", "./bar/../wwwroot", "foo/../wwwroot", "wwwroot/." }) + { + yield return new object[] { webRoot, (CreateBuilderOptionsFunc)CreateBuilderOptions }; + yield return new object[] { webRoot, (CreateBuilderOptionsFunc)CreateSlimBuilderOptions }; + } + } + } + [Theory] - [InlineData("wwwroot")] - [InlineData("./wwwroot")] - [InlineData("./bar/../wwwroot")] - [InlineData("foo/../wwwroot")] - [InlineData("wwwroot/.")] - public void WebApplicationBuilder_CanHandleVariousWebRootPaths_OverrideDefaultPath(string webRoot) + [MemberData(nameof(CanHandleVariousWebRootPaths_OverrideDefaultPathData))] + public void WebApplicationBuilder_CanHandleVariousWebRootPaths_OverrideDefaultPath(string webRoot, CreateBuilderOptionsFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -425,7 +512,7 @@ public void WebApplicationBuilder_CanHandleVariousWebRootPaths_OverrideDefaultPa ContentRootPath = contentRoot }; - var builder = new WebApplicationBuilder(options); + var builder = createBuilder(options); Assert.Equal(contentRoot, builder.Environment.ContentRootPath); Assert.Equal(fullWebRootPath, builder.Environment.WebRootPath); @@ -438,12 +525,24 @@ public void WebApplicationBuilder_CanHandleVariousWebRootPaths_OverrideDefaultPa } } + public static IEnumerable SettingContentRootToRelativePathData + { + get + { + // Empty behaves differently to null + foreach (string path in new[] { "", "." }) + { + yield return new object[] { path, (CreateBuilderOptionsFunc)CreateBuilderOptions }; + yield return new object[] { path, (CreateBuilderOptionsFunc)CreateSlimBuilderOptions }; + } + } + } + [Theory] - [InlineData("")] // Empty behaves differently to null - [InlineData(".")] - public void SettingContentRootToRelativePathUsesAppContextBaseDirectoryAsPathBase(string path) + [MemberData(nameof(SettingContentRootToRelativePathData))] + public void SettingContentRootToRelativePathUsesAppContextBaseDirectoryAsPathBase(string path, CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions + var builder = createBuilder(new WebApplicationOptions { ContentRootPath = path }); @@ -462,8 +561,9 @@ static string NormalizePath(string unnormalizedPath) => Path.TrimEndingDirectorySeparator(Path.GetFullPath(unnormalizedPath)); } - [Fact] - public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemblyLoadForUserSecretsFail() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemblyLoadForUserSecretsFail(CreateBuilderOptionsFunc createBuilder) { var options = new WebApplicationOptions { @@ -472,14 +572,15 @@ public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemb }; // Use secrets fails to load an invalid assembly name but does not throw - var webApplication = WebApplication.CreateBuilder(options).Build(); + var webApplication = createBuilder(options).Build(); Assert.Equal(nameof(WebApplicationTests), webApplication.Environment.ApplicationName); Assert.Equal(Environments.Development, webApplication.Environment.EnvironmentName); } - [Fact] - public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptions() + [Theory] + [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] + public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptions(WebApplicationBuilderConstructorFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -497,7 +598,7 @@ public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOpti WebRootPath = webRoot }; - var builder = new WebApplicationBuilder( + var builder = createBuilder( options, bootstrapBuilder => { @@ -520,8 +621,9 @@ public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOpti } } - [Fact] - public void WebApplicationBuilderWebApplicationOptionsPropertiesOverridesArgs() + [Theory] + [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] + public void WebApplicationBuilderWebApplicationOptionsPropertiesOverridesArgs(WebApplicationBuilderConstructorFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -545,7 +647,7 @@ public void WebApplicationBuilderWebApplicationOptionsPropertiesOverridesArgs() WebRootPath = webRoot }; - var builder = new WebApplicationBuilder( + var builder = createBuilder( options, bootstrapBuilder => { @@ -568,8 +670,9 @@ public void WebApplicationBuilderWebApplicationOptionsPropertiesOverridesArgs() } } - [Fact] - public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptionsArgs() + [Theory] + [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] + public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptionsArgs(WebApplicationBuilderConstructorFunc createBuilder) { var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(contentRoot); @@ -590,7 +693,7 @@ public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOpti } }; - var builder = new WebApplicationBuilder( + var builder = createBuilder( options, bootstrapBuilder => { @@ -613,12 +716,13 @@ public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOpti } } - [Fact] - public void WebApplicationBuilderApplicationNameDefaultsToEntryAssembly() + [Theory] + [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] + public void WebApplicationBuilderApplicationNameDefaultsToEntryAssembly(WebApplicationBuilderConstructorFunc createBuilder) { var assemblyName = Assembly.GetEntryAssembly().GetName().Name; - var builder = new WebApplicationBuilder( + var builder = createBuilder( new(), bootstrapBuilder => { @@ -649,8 +753,9 @@ public void WebApplicationBuilderApplicationNameDefaultsToEntryAssembly() Assert.Equal(assemblyName, webHostEnv.ApplicationName); } - [Fact] - public void WebApplicationBuilderApplicationNameCanBeOverridden() + [Theory] + [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] + public void WebApplicationBuilderApplicationNameCanBeOverridden(WebApplicationBuilderConstructorFunc createBuilder) { var assemblyName = typeof(WebApplicationTests).Assembly.GetName().Name; @@ -659,7 +764,7 @@ public void WebApplicationBuilderApplicationNameCanBeOverridden() ApplicationName = assemblyName }; - var builder = new WebApplicationBuilder( + var builder = createBuilder( options, bootstrapBuilder => { @@ -690,10 +795,11 @@ public void WebApplicationBuilderApplicationNameCanBeOverridden() Assert.Equal(assemblyName, webHostEnv.ApplicationName); } - [Fact] - public void WebApplicationBuilderCanFlowCommandLineConfigurationToApplication() + [Theory] + [MemberData(nameof(CreateBuilderArgsFuncs))] + public void WebApplicationBuilderCanFlowCommandLineConfigurationToApplication(CreateBuilderArgsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new[] { "--x=1", "--name=Larry", "--age=20", "--environment=Testing" }); + var builder = createBuilder(new[] { "--x=1", "--name=Larry", "--age=20", "--environment=Testing" }); Assert.Equal("1", builder.Configuration["x"]); Assert.Equal("Larry", builder.Configuration["name"]); @@ -723,10 +829,11 @@ public void WebApplicationBuilderCanFlowCommandLineConfigurationToApplication() Assert.Equal("Testing", app.Configuration["environment"]); } - [Fact] - public void WebApplicationBuilderHostBuilderSettingsThatAffectTheHostCannotBeModified() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderHostBuilderSettingsThatAffectTheHostCannotBeModified(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var contentRoot = Path.GetTempPath().ToString(); var envName = $"{nameof(WebApplicationTests)}_ENV"; @@ -743,10 +850,11 @@ public void WebApplicationBuilderHostBuilderSettingsThatAffectTheHostCannotBeMod Assert.Throws(() => builder.Host.UseContentRoot(contentRoot)); } - [Fact] - public void WebApplicationBuilderWebHostUseSettingCanBeReadByConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderWebHostUseSettingCanBeReadByConfiguration(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseSetting("A", "value"); builder.WebHost.UseSetting("B", "another"); @@ -763,8 +871,9 @@ public void WebApplicationBuilderWebHostUseSettingCanBeReadByConfiguration() Assert.Equal("another", builder.Configuration["B"]); } - [Fact] - public async Task WebApplicationCanObserveConfigurationChangesMadeInBuild() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationCanObserveConfigurationChangesMadeInBuild(CreateBuilderFunc createBuilder) { // This mimics what WebApplicationFactory does and runs configure // services callbacks @@ -802,7 +911,7 @@ public async Task WebApplicationCanObserveConfigurationChangesMadeInBuild() }); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); await using var app = builder.Build(); @@ -821,8 +930,9 @@ public async Task WebApplicationCanObserveConfigurationChangesMadeInBuild() Assert.Equal("F", builder.Configuration["F"]); } - [Fact] - public async Task WebApplicationCanObserveSourcesClearedInBuild() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationCanObserveSourcesClearedInBuild(CreateBuilderFunc createBuilder) { // This mimics what WebApplicationFactory does and runs configure // services callbacks @@ -847,7 +957,7 @@ public async Task WebApplicationCanObserveSourcesClearedInBuild() }); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Configuration.AddInMemoryCollection(new Dictionary() { @@ -864,8 +974,9 @@ public async Task WebApplicationCanObserveSourcesClearedInBuild() Assert.Same(builder.Configuration, app.Configuration); } - [Fact] - public async Task WebApplicationCanObserveSourcesClearedInConfiguratHostConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task WebApplicationCanObserveSourcesClearedInConfiguratHostConfiguration(CreateBuilderOptionsFunc createBuilder) { // This mimics what WebApplicationFactory does and runs configure // services callbacks @@ -885,7 +996,7 @@ public async Task WebApplicationCanObserveSourcesClearedInConfiguratHostConfigur }); }); - var builder = WebApplication.CreateBuilder(new WebApplicationOptions + var builder = createBuilder(new WebApplicationOptions { ApplicationName = "appName", EnvironmentName = "environmentName", @@ -906,8 +1017,9 @@ public async Task WebApplicationCanObserveSourcesClearedInConfiguratHostConfigur Assert.Same(builder.Configuration, app.Configuration); } - [Fact] - public async Task WebApplicationCanHandleStreamBackedConfigurationAddedInBuild() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationCanHandleStreamBackedConfigurationAddedInBuild(CreateBuilderFunc createBuilder) { static Stream CreateStreamFromString(string data) => new MemoryStream(Encoding.UTF8.GetBytes(data)); @@ -922,7 +1034,7 @@ public async Task WebApplicationCanHandleStreamBackedConfigurationAddedInBuild() hostBuilder.ConfigureAppConfiguration(config => config.AddJsonStream(jsonBStream)); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); await using var app = builder.Build(); Assert.Equal("A", app.Configuration["A"]); @@ -931,8 +1043,9 @@ public async Task WebApplicationCanHandleStreamBackedConfigurationAddedInBuild() Assert.Same(builder.Configuration, app.Configuration); } - [Fact] - public async Task WebApplicationDisposesConfigurationProvidersAddedInBuild() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationDisposesConfigurationProvidersAddedInBuild(CreateBuilderFunc createBuilder) { var hostConfigSource = new RandomConfigurationSource(); var appConfigSource = new RandomConfigurationSource(); @@ -945,7 +1058,7 @@ public async Task WebApplicationDisposesConfigurationProvidersAddedInBuild() hostBuilder.ConfigureAppConfiguration(config => config.Add(appConfigSource)); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); { await using var app = builder.Build(); @@ -966,8 +1079,9 @@ public async Task WebApplicationDisposesConfigurationProvidersAddedInBuild() Assert.Equal(1, appConfigSource.ProvidersDisposed); } - [Fact] - public async Task WebApplicationMakesOriginalConfigurationProvidersAddedInBuildAccessable() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationMakesOriginalConfigurationProvidersAddedInBuildAccessable(CreateBuilderFunc createBuilder) { // This mimics what WebApplicationFactory does and runs configure // services callbacks @@ -976,16 +1090,17 @@ public async Task WebApplicationMakesOriginalConfigurationProvidersAddedInBuildA hostBuilder.ConfigureAppConfiguration(config => config.Add(new RandomConfigurationSource())); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); await using var app = builder.Build(); Assert.Single(((IConfigurationRoot)app.Configuration).Providers.OfType()); } - [Fact] - public void WebApplicationBuilderHostProperties_IsCaseSensitive() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilderHostProperties_IsCaseSensitive(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Host.Properties["lowercase"] = nameof(WebApplicationTests); @@ -993,10 +1108,11 @@ public void WebApplicationBuilderHostProperties_IsCaseSensitive() Assert.False(builder.Host.Properties.ContainsKey("Lowercase")); } - [Fact] - public async Task WebApplicationConfiguration_HostFilterOptionsAreReloadable() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationConfiguration_HostFilterOptionsAreReloadable(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var host = builder.WebHost .ConfigureAppConfiguration(configBuilder => { @@ -1023,10 +1139,11 @@ public async Task WebApplicationConfiguration_HostFilterOptionsAreReloadable() Assert.Contains("NewHost", options.AllowedHosts); } - [Fact] - public void CanResolveIConfigurationBeforeBuildingApplication() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void CanResolveIConfigurationBeforeBuildingApplication(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var sp = builder.Services.BuildServiceProvider(); var config = sp.GetService(); @@ -1038,10 +1155,11 @@ public void CanResolveIConfigurationBeforeBuildingApplication() Assert.Same(app.Configuration, builder.Configuration); } - [Fact] - public void ManuallyAddingConfigurationAsServiceWorks() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ManuallyAddingConfigurationAsServiceWorks(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Services.AddSingleton(builder.Configuration); var sp = builder.Services.BuildServiceProvider(); @@ -1054,10 +1172,11 @@ public void ManuallyAddingConfigurationAsServiceWorks() Assert.Same(app.Configuration, builder.Configuration); } - [Fact] - public void AddingMemoryStreamBackedConfigurationWorks() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void AddingMemoryStreamBackedConfigurationWorks(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var jsonConfig = @"{ ""foo"": ""bar"" }"; using var ms = new MemoryStream(); @@ -1075,10 +1194,11 @@ public void AddingMemoryStreamBackedConfigurationWorks() Assert.Equal("bar", app.Configuration["foo"]); } - [Fact] - public async Task WebApplicationConfiguration_EnablesForwardedHeadersFromConfig() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationConfiguration_EnablesForwardedHeadersFromConfig(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); builder.Configuration["FORWARDEDHEADERS_ENABLED"] = "true"; await using var app = builder.Build(); @@ -1105,10 +1225,11 @@ public void WebApplicationCreate_RegistersRouting() Assert.NotNull(linkGenerator); } - [Fact] - public void WebApplication_CanResolveDefaultServicesFromServiceCollection() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplication_CanResolveDefaultServicesFromServiceCollection(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); // Add the service collection to the service collection builder.Services.AddSingleton(builder.Services); @@ -1124,8 +1245,9 @@ public void WebApplication_CanResolveDefaultServicesFromServiceCollection() Assert.Equal(env0.ContentRootPath, env1.ContentRootPath); } - [Fact] - public async Task WebApplication_CanResolveServicesAddedAfterBuildFromServiceCollection() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplication_CanResolveServicesAddedAfterBuildFromServiceCollection(CreateBuilderFunc createBuilder) { // This mimics what WebApplicationFactory does and runs configure // services callbacks @@ -1137,7 +1259,7 @@ public async Task WebApplication_CanResolveServicesAddedAfterBuildFromServiceCol }); }); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); // Add the service collection to the service collection builder.Services.AddSingleton(builder.Services); @@ -1152,10 +1274,11 @@ public async Task WebApplication_CanResolveServicesAddedAfterBuildFromServiceCol Assert.IsType(service1); } - [Fact] - public async Task WebApplication_CanResolveIConfigurationFromServiceCollection() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplication_CanResolveIConfigurationFromServiceCollection(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Configuration.AddInMemoryCollection(new Dictionary { @@ -1178,10 +1301,11 @@ public async Task WebApplication_CanResolveIConfigurationFromServiceCollection() Assert.Equal("bar", app.Configuration["foo"]); } - [Fact] - public void WebApplication_CanResolveDefaultServicesFromServiceCollectionInCorrectOrder() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplication_CanResolveDefaultServicesFromServiceCollectionInCorrectOrder(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); // Add the service collection to the service collection builder.Services.AddSingleton(builder.Services); @@ -1205,10 +1329,11 @@ public void WebApplication_CanResolveDefaultServicesFromServiceCollectionInCorre Assert.Equal(hostLifetimes1.Length, hostLifetimes0.Length); } - [Fact] - public async Task WebApplication_CanCallUseRoutingWithoutUseEndpoints() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplication_CanCallUseRoutingWithoutUseEndpoints(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1243,10 +1368,11 @@ public async Task WebApplication_CanCallUseRoutingWithoutUseEndpoints() Assert.Equal("new", await oldResult.Content.ReadAsStringAsync()); } - [Fact] - public async Task WebApplication_CanCallUseEndpointsWithoutUseRoutingFails() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplication_CanCallUseEndpointsWithoutUseRoutingFails(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1272,11 +1398,12 @@ public void WebApplicationCreate_RegistersEventSourceLogger() args.Payload.OfType().Any(p => p.Contains(guid))); } - [Fact] - public void WebApplicationBuilder_CanClearDefaultLoggers() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilder_CanClearDefaultLoggers(CreateBuilderFunc createBuilder) { var listener = new TestEventListener(); - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Logging.ClearProviders(); var app = builder.Build(); @@ -1291,10 +1418,11 @@ public void WebApplicationBuilder_CanClearDefaultLoggers() args.Payload.OfType().Any(p => p.Contains(guid))); } - [Fact] - public async Task WebApplicationBuilder_StartupFilterCanAddTerminalMiddleware() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationBuilder_StartupFilterCanAddTerminalMiddleware(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); builder.Services.AddSingleton(); await using var app = builder.Build(); @@ -1312,10 +1440,11 @@ public async Task WebApplicationBuilder_StartupFilterCanAddTerminalMiddleware() Assert.Equal(418, (int)terminalResult.StatusCode); } - [Fact] - public async Task StartupFilter_WithUseRoutingWorks() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task StartupFilter_WithUseRoutingWorks(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); builder.Services.AddSingleton(); await using var app = builder.Build(); @@ -1338,10 +1467,11 @@ public async Task StartupFilter_WithUseRoutingWorks() Assert.Equal(203, ((int)response.StatusCode)); } - [Fact] - public async Task CanAddMiddlewareBeforeUseRouting() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task CanAddMiddlewareBeforeUseRouting(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1375,10 +1505,11 @@ public async Task CanAddMiddlewareBeforeUseRouting() Assert.Equal("One", chosenEndpoint); } - [Fact] - public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); // IWebHostEnvironment is added by ConfigureDefaults Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions))); @@ -1395,11 +1526,12 @@ public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce() Assert.Single(app.Services.GetRequiredService>()); } - [Fact] - public void WebApplicationBuilder_EnablesServiceScopeValidationByDefaultInDevelopment() + [Theory] + [MemberData(nameof(CreateBuilderArgsFuncs))] + public void WebApplicationBuilder_EnablesServiceScopeValidationByDefaultInDevelopment(CreateBuilderArgsFunc createBuilder) { // The environment cannot be reconfigured after the builder is created currently. - var builder = WebApplication.CreateBuilder(new[] { "--environment", "Development" }); + var builder = createBuilder(new[] { "--environment", "Development" }); builder.Services.AddScoped(); builder.Services.AddSingleton(); @@ -1409,10 +1541,11 @@ public void WebApplicationBuilder_EnablesServiceScopeValidationByDefaultInDevelo Assert.ThrowsAny(() => builder.Build()); } - [Fact] - public async Task WebApplicationBuilder_ThrowsExceptionIfServicesAlreadyBuilt() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationBuilder_ThrowsExceptionIfServicesAlreadyBuilt(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); await using var app = builder.Build(); Assert.Throws(() => builder.Services.AddSingleton(new Service())); @@ -1423,10 +1556,11 @@ public async Task WebApplicationBuilder_ThrowsExceptionIfServicesAlreadyBuilt() Assert.Throws(() => builder.Services[0] = ServiceDescriptor.Singleton(new Service())); } - [Fact] - public void WebApplicationBuilder_ThrowsFromExtensionMethodsNotSupportedByHostAndWebHost() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void WebApplicationBuilder_ThrowsFromExtensionMethodsNotSupportedByHostAndWebHost(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var ex = Assert.Throws(() => builder.WebHost.Configure(app => { })); var ex1 = Assert.Throws(() => builder.WebHost.Configure((context, app) => { })); @@ -1442,17 +1576,20 @@ public void WebApplicationBuilder_ThrowsFromExtensionMethodsNotSupportedByHostAn var ex5 = Assert.Throws(() => builder.Host.ConfigureWebHost(webHostBuilder => { })); var ex6 = Assert.Throws(() => builder.Host.ConfigureWebHost(webHostBuilder => { }, options => { })); - var ex7 = Assert.Throws(() => builder.Host.ConfigureWebHostDefaults(webHostBuilder => { })); + var ex7 = Assert.Throws(() => builder.Host.ConfigureSlimWebHost(webHostBuilder => { }, options => { })); + var ex8 = Assert.Throws(() => builder.Host.ConfigureWebHostDefaults(webHostBuilder => { })); Assert.Equal("ConfigureWebHost() is not supported by WebApplicationBuilder.Host. Use the WebApplication returned by WebApplicationBuilder.Build() instead.", ex5.Message); Assert.Equal("ConfigureWebHost() is not supported by WebApplicationBuilder.Host. Use the WebApplication returned by WebApplicationBuilder.Build() instead.", ex6.Message); Assert.Equal("ConfigureWebHost() is not supported by WebApplicationBuilder.Host. Use the WebApplication returned by WebApplicationBuilder.Build() instead.", ex7.Message); + Assert.Equal("ConfigureWebHost() is not supported by WebApplicationBuilder.Host. Use the WebApplication returned by WebApplicationBuilder.Build() instead.", ex8.Message); } - [Fact] - public async Task EndpointDataSourceOnlyAddsOnce() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task EndpointDataSourceOnlyAddsOnce(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); await using var app = builder.Build(); app.UseRouting(); @@ -1474,10 +1611,11 @@ public async Task EndpointDataSourceOnlyAddsOnce() Assert.Equal("Three", ds.Endpoints[2].DisplayName); } - [Fact] - public async Task RoutesAddedToCorrectMatcher() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task RoutesAddedToCorrectMatcher(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1509,10 +1647,11 @@ public async Task RoutesAddedToCorrectMatcher() Assert.Equal("One", chosenRoute); } - [Fact] - public async Task WebApplication_CallsUseRoutingAndUseEndpoints() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplication_CallsUseRoutingAndUseEndpoints(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1535,10 +1674,11 @@ public async Task WebApplication_CallsUseRoutingAndUseEndpoints() Assert.Equal("One", chosenRoute); } - [Fact] - public async Task BranchingPipelineHasOwnRoutes() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task BranchingPipelineHasOwnRoutes(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1599,10 +1739,11 @@ public async Task BranchingPipelineHasOwnRoutes() Assert.Equal("Four", chosenRoute); } - [Fact] - public async Task PropertiesPreservedFromInnerApplication() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task PropertiesPreservedFromInnerApplication(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Services.AddSingleton(); await using var app = builder.Build(); @@ -1611,10 +1752,11 @@ public async Task PropertiesPreservedFromInnerApplication() app.Start(); } - [Fact] - public async Task DeveloperExceptionPageIsOnByDefaltInDevelopment() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task DeveloperExceptionPageIsOnByDefaltInDevelopment(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); + var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1632,10 +1774,11 @@ public async Task DeveloperExceptionPageIsOnByDefaltInDevelopment() Assert.Contains("text/plain", response.Content.Headers.ContentType.MediaType); } - [Fact] - public async Task DeveloperExceptionPageDoesNotGetCaughtByStartupFilters() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task DeveloperExceptionPageDoesNotGetCaughtByStartupFilters(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); + var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); builder.WebHost.UseTestServer(); builder.Services.AddSingleton(); await using var app = builder.Build(); @@ -1649,10 +1792,11 @@ public async Task DeveloperExceptionPageDoesNotGetCaughtByStartupFilters() Assert.Equal("BOOM Filter", ex.Message); } - [Fact] - public async Task DeveloperExceptionPageIsNotOnInProduction() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task DeveloperExceptionPageIsNotOnInProduction(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Production }); + var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Production }); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1670,6 +1814,7 @@ public async Task DeveloperExceptionPageIsNotOnInProduction() [Fact] public async Task HostingStartupRunsWhenApplicationIsNotEntryPoint() { + // NOTE: CreateSlimBuilder doesn't support Startups var builder = WebApplication.CreateBuilder(new WebApplicationOptions { ApplicationName = typeof(WebApplicationTests).Assembly.FullName }); await using var app = builder.Build(); @@ -1679,6 +1824,7 @@ public async Task HostingStartupRunsWhenApplicationIsNotEntryPoint() [Fact] public async Task HostingStartupRunsWhenApplicationIsNotEntryPointWithArgs() { + // NOTE: CreateSlimBuilder doesn't support Startups var builder = WebApplication.CreateBuilder(new[] { "--applicationName", typeof(WebApplicationTests).Assembly.FullName }); await using var app = builder.Build(); @@ -1693,16 +1839,18 @@ public async Task HostingStartupRunsWhenApplicationIsNotEntryPointApplicationNam Args = new[] { "--applicationName", typeof(WebApplication).Assembly.FullName }, ApplicationName = typeof(WebApplicationTests).Assembly.FullName, }; + // NOTE: CreateSlimBuilder doesn't support Startups var builder = WebApplication.CreateBuilder(options); await using var app = builder.Build(); Assert.Equal("value", app.Configuration["testhostingstartup:config"]); } - [Fact] - public async Task DeveloperExceptionPageWritesBadRequestDetailsToResponseByDefaltInDevelopment() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task DeveloperExceptionPageWritesBadRequestDetailsToResponseByDefaltInDevelopment(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); + var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1723,10 +1871,11 @@ public async Task DeveloperExceptionPageWritesBadRequestDetailsToResponseByDefal Assert.Contains("notAnInt", responseBody); } - [Fact] - public async Task NoExceptionAreThrownForBadRequestsInProduction() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public async Task NoExceptionAreThrownForBadRequestsInProduction(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Production }); + var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Production }); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1746,10 +1895,11 @@ public async Task NoExceptionAreThrownForBadRequestsInProduction() Assert.Equal(string.Empty, responseBody); } - [Fact] - public void PropertiesArePropagated() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void PropertiesArePropagated(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Host.Properties["hello"] = "world"; var callbacks = 0; @@ -1777,8 +1927,9 @@ public void PropertiesArePropagated() Assert.Equal(0b00000111, callbacks); } - [Fact] - public void EmptyAppConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void EmptyAppConfiguration(CreateBuilderFunc createBuilder) { var wwwroot = Path.Combine(AppContext.BaseDirectory, "wwwroot"); bool createdDirectory = false; @@ -1790,7 +1941,7 @@ public void EmptyAppConfiguration() try { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.ConfigureAppConfiguration((ctx, config) => { }); @@ -1807,10 +1958,11 @@ public void EmptyAppConfiguration() } } - [Fact] - public void HostConfigurationNotAffectedByConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void HostConfigurationNotAffectedByConfiguration(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var contentRoot = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); var envName = $"{nameof(WebApplicationTests)}_ENV"; @@ -1836,10 +1988,11 @@ public void HostConfigurationNotAffectedByConfiguration() Assert.NotEqual(contentRoot, hostEnv.ContentRootPath); } - [Fact] - public void ClearingConfigurationDoesNotAffectHostConfiguration() + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public void ClearingConfigurationDoesNotAffectHostConfiguration(CreateBuilderOptionsFunc createBuilder) { - var builder = WebApplication.CreateBuilder(new WebApplicationOptions + var builder = createBuilder(new WebApplicationOptions { ApplicationName = typeof(WebApplicationOptions).Assembly.FullName, EnvironmentName = Environments.Staging, @@ -1865,10 +2018,11 @@ public void ClearingConfigurationDoesNotAffectHostConfiguration() Assert.Equal(Path.GetTempPath(), hostEnv.ContentRootPath); } - [Fact] - public void ConfigurationGetDebugViewWorks() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationGetDebugViewWorks(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.Configuration.AddInMemoryCollection(new Dictionary { @@ -1881,10 +2035,11 @@ public void ConfigurationGetDebugViewWorks() Assert.Contains("foo=bar (MemoryConfigurationProvider)", ((IConfigurationRoot)app.Configuration).GetDebugView()); } - [Fact] - public void ConfigurationCanBeReloaded() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationCanBeReloaded(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); ((IConfigurationBuilder)builder.Configuration).Sources.Add(new RandomConfigurationSource()); @@ -1897,10 +2052,11 @@ public void ConfigurationCanBeReloaded() Assert.NotEqual(value0, value1); } - [Fact] - public void ConfigurationSourcesAreBuiltOnce() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationSourcesAreBuiltOnce(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var configSource = new RandomConfigurationSource(); ((IConfigurationBuilder)builder.Configuration).Sources.Add(configSource); @@ -1910,10 +2066,11 @@ public void ConfigurationSourcesAreBuiltOnce() Assert.Equal(1, configSource.ProvidersBuilt); } - [Fact] - public void ConfigurationProvidersAreLoadedOnceAfterBuild() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationProvidersAreLoadedOnceAfterBuild(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var configSource = new RandomConfigurationSource(); ((IConfigurationBuilder)builder.Configuration).Sources.Add(configSource); @@ -1923,10 +2080,11 @@ public void ConfigurationProvidersAreLoadedOnceAfterBuild() Assert.Equal(1, configSource.ProvidersLoaded); } - [Fact] - public void ConfigurationProvidersAreDisposedWithWebApplication() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationProvidersAreDisposedWithWebApplication(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); var configSource = new RandomConfigurationSource(); ((IConfigurationBuilder)builder.Configuration).Sources.Add(configSource); @@ -1940,10 +2098,11 @@ public void ConfigurationProvidersAreDisposedWithWebApplication() Assert.Equal(1, configSource.ProvidersDisposed); } - [Fact] - public void ConfigurationProviderTypesArePreserved() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void ConfigurationProviderTypesArePreserved(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); ((IConfigurationBuilder)builder.Configuration).Sources.Add(new RandomConfigurationSource()); @@ -1952,10 +2111,11 @@ public void ConfigurationProviderTypesArePreserved() Assert.Single(((IConfigurationRoot)app.Configuration).Providers.OfType()); } - [Fact] - public async Task CanUseMiddleware() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public async Task CanUseMiddleware(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); builder.WebHost.UseTestServer(); await using var app = builder.Build(); @@ -1972,10 +2132,11 @@ public async Task CanUseMiddleware() Assert.Equal("Hello World", response); } - [Fact] - public void CanObserveDefaultServicesInServiceCollection() + [Theory] + [MemberData(nameof(CreateBuilderFuncs))] + public void CanObserveDefaultServicesInServiceCollection(CreateBuilderFunc createBuilder) { - var builder = WebApplication.CreateBuilder(); + var builder = createBuilder(); Assert.Contains(builder.Services, service => service.ServiceType == typeof(HostBuilderContext)); Assert.Contains(builder.Services, service => service.ServiceType == typeof(IHostApplicationLifetime)); @@ -1985,20 +2146,32 @@ public void CanObserveDefaultServicesInServiceCollection() Assert.Contains(builder.Services, service => service.ServiceType == typeof(ILogger<>)); } - [Fact] - public async Task RegisterAuthMiddlewaresCorrectly() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task RegisterAuthMiddlewaresCorrectly(bool useSlimBuilder) { var helloEndpointCalled = false; var customMiddlewareExecuted = false; var username = "foobar"; - var builder = WebApplication.CreateBuilder(); + var builder = useSlimBuilder ? + WebApplication.CreateSlimBuilder() : + WebApplication.CreateBuilder(); + builder.Services.AddAuthorization(); builder.Services.AddAuthentication("testSchemeName") .AddScheme("testSchemeName", "testDisplayName", _ => { }); builder.WebHost.UseTestServer(); await using var app = builder.Build(); + if (useSlimBuilder) + { + // NOTE: CreateSlimBuilder doesn't support auto registration of auth middleware, so need to do it explicitly + app.UseAuthentication(); + app.UseAuthorization(); + } + app.Use(next => { return async context => @@ -2031,6 +2204,7 @@ public async Task RegisterAuthMiddlewaresCorrectly() [Fact] public async Task SupportsDisablingMiddlewareAutoRegistration() { + // NOTE: CreateSlimBuilder doesn't support auto registration of auth middleware var builder = WebApplication.CreateBuilder(); builder.Services.AddAuthorization(); builder.Services.AddAuthentication("testSchemeName") diff --git a/src/Hosting/Hosting/src/GenericHost/SlimWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/SlimWebHostBuilder.cs new file mode 100644 index 000000000000..e109b45898a9 --- /dev/null +++ b/src/Hosting/Hosting/src/GenericHost/SlimWebHostBuilder.cs @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting.Builder; +using Microsoft.AspNetCore.Hosting.Infrastructure; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AspNetCore.Hosting; + +internal sealed class SlimWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider +{ + private readonly IHostBuilder _builder; + private readonly IConfiguration _config; + + public SlimWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) + { + _builder = builder; + var configBuilder = new ConfigurationBuilder() + .AddInMemoryCollection(); + + if (!options.SuppressEnvironmentConfiguration) + { + configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); + } + + _config = configBuilder.Build(); + + _builder.ConfigureHostConfiguration(config => + { + config.AddConfiguration(_config); + }); + + _builder.ConfigureServices((context, services) => + { + var webhostContext = GetWebHostBuilderContext(context); + var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; + + // Add the IHostingEnvironment and IApplicationLifetime from Microsoft.AspNetCore.Hosting + services.AddSingleton(webhostContext.HostingEnvironment); +#pragma warning disable CS0618 // Type or member is obsolete + services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment); + services.AddSingleton(); +#pragma warning restore CS0618 // Type or member is obsolete + + services.Configure(options => + { + // Set the options + options.WebHostOptions = webHostOptions; + }); + + // REVIEW: This is bad since we don't own this type. Anybody could add one of these and it would mess things up + // We need to flow this differently + services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore")); + services.TryAddSingleton(sp => sp.GetRequiredService()); + services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore")); + services.TryAddSingleton(DistributedContextPropagator.Current); + + services.TryAddSingleton(); + services.TryAddScoped(); + services.TryAddSingleton(); + }); + } + + public IWebHost Build() + { + throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); + } + + public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) + { + _builder.ConfigureAppConfiguration((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureDelegate(webhostBuilderContext, builder); + }); + + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + return ConfigureServices((context, services) => configureServices(services)); + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _builder.ConfigureServices((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureServices(webhostBuilderContext, builder); + }); + + return this; + } + + public IWebHostBuilder UseDefaultServiceProvider(Action configure) + { + _builder.UseServiceProviderFactory(context => + { + var webHostBuilderContext = GetWebHostBuilderContext(context); + var options = new ServiceProviderOptions(); + configure(webHostBuilderContext, options); + // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 +#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + return new DefaultServiceProviderFactory(options); +#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. + }); + + return this; + } + + public IWebHostBuilder Configure(Action configure) + { + throw new NotSupportedException(); + } + + public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] Type startupType) + { + throw new NotSupportedException(); + } + + public IWebHostBuilder UseStartup<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TStartup>(Func startupFactory) + { + throw new NotSupportedException(); + } + + public IWebHostBuilder Configure(Action configure) + { + var startupAssemblyName = configure.GetMethodInfo().DeclaringType!.Assembly.GetName().Name!; + + UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName); + + _builder.ConfigureServices((context, services) => + { + services.Configure(options => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + options.ConfigureApplication = app => configure(webhostBuilderContext, app); + }); + }); + + return this; + } + + private WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context) + { + if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) + { + // Use _config as a fallback for WebHostOptions in case the chained source was removed from the hosting IConfigurationBuilder. + var options = new WebHostOptions(context.Configuration, fallbackConfiguration: _config, environment: context.HostingEnvironment); + var webHostBuilderContext = new WebHostBuilderContext + { + Configuration = context.Configuration, + HostingEnvironment = new HostingEnvironment(), + }; + webHostBuilderContext.HostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options, baseEnvironment: context.HostingEnvironment); + context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext; + context.Properties[typeof(WebHostOptions)] = options; + return webHostBuilderContext; + } + + // Refresh config, it's periodically updated/replaced + var webHostContext = (WebHostBuilderContext)contextVal; + webHostContext.Configuration = context.Configuration; + return webHostContext; + } + + public string? GetSetting(string key) + { + return _config[key]; + } + + public IWebHostBuilder UseSetting(string key, string? value) + { + _config[key] = value; + return this; + } +} diff --git a/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs index 7bd6f86a49af..f874d54f3995 100644 --- a/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs +++ b/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs @@ -33,6 +33,35 @@ public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, ActionThe delegate that configures the . /// The . public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action configure, Action configureWebHostBuilder) + { + return ConfigureWebHost( + builder, + (hostBuilder, options) => new GenericWebHostBuilder(hostBuilder, options), + configure, + configureWebHostBuilder); + } + + /// + /// Adds and configures an ASP.NET Core web application with minimal dependencies. + /// + /// The to add the to. + /// The delegate that configures the . + /// The delegate that configures the . + /// The . + public static IHostBuilder ConfigureSlimWebHost(this IHostBuilder builder, Action configure, Action configureWebHostBuilder) + { + return ConfigureWebHost( + builder, + (hostBuilder, options) => new SlimWebHostBuilder(hostBuilder, options), + configure, + configureWebHostBuilder); + } + + private static IHostBuilder ConfigureWebHost( + this IHostBuilder builder, + Func createWebHostBuilder, + Action configure, + Action configureWebHostBuilder) { ArgumentNullException.ThrowIfNull(configure); ArgumentNullException.ThrowIfNull(configureWebHostBuilder); @@ -45,7 +74,7 @@ public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action services.AddHostedService()); return builder; diff --git a/src/Hosting/Hosting/src/PublicAPI.Unshipped.txt b/src/Hosting/Hosting/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..de9a58f9e603 100644 --- a/src/Hosting/Hosting/src/PublicAPI.Unshipped.txt +++ b/src/Hosting/Hosting/src/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +static Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureSlimWebHost(this Microsoft.Extensions.Hosting.IHostBuilder! builder, System.Action! configure, System.Action! configureWebHostBuilder) -> Microsoft.Extensions.Hosting.IHostBuilder! From 743349d6395358897cea9fc4bf4835bb3bc16c21 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 18 Jan 2023 17:06:32 -0600 Subject: [PATCH 2/7] Use the new slim hosting API in the api template. --- .../Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs | 2 +- .../Web.ProjectTemplates/content/Api-CSharp/Program.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs index 1e7082c4f9cb..dc6deae4de45 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs @@ -9,7 +9,7 @@ public class Program { public static void Main(string[] args) { - var builder = WebApplication.CreateBuilder(args); + var builder = WebApplication.CreateSlimBuilder(args); builder.Logging.AddConsole(); #if (NativeAot) diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs index be0ffccde6fa..5f12bdd3c5ba 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs @@ -3,7 +3,7 @@ #endif using Company.ApiApplication1; -var builder = WebApplication.CreateBuilder(args); +var builder = WebApplication.CreateSlimBuilder(args); builder.Logging.AddConsole(); #if (NativeAot) From ef62c6d2eca2487095fe88f76cd3f9949f42c077 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 18 Jan 2023 18:26:18 -0600 Subject: [PATCH 3/7] Makes changes for the latest code. Refactor the WebHostBuilder classes to share more code. --- .../src/WebApplicationBuilder.cs | 3 - .../src/GenericHost/GenericWebHostBuilder.cs | 95 +--------------- .../src/GenericHost/SlimWebHostBuilder.cs | 99 +--------------- .../src/GenericHost/WebHostBuilderBase.cs | 107 ++++++++++++++++++ .../GenericHostWebHostBuilderExtensions.cs | 4 +- 5 files changed, 113 insertions(+), 195 deletions(-) create mode 100644 src/Hosting/Hosting/src/GenericHost/WebHostBuilderBase.cs diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 5792ed94169a..c911651c45ee 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -92,8 +92,6 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< var configuration = new ConfigurationManager(); - // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 -#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings { Args = options.Args, @@ -102,7 +100,6 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< ContentRootPath = options.ContentRootPath, Configuration = configuration, }); -#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. // Set WebRootPath if necessary if (options.WebRootPath is not null) diff --git a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs index 0116dd0c8410..d89ddfe9331c 100644 --- a/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs +++ b/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs @@ -18,10 +18,8 @@ namespace Microsoft.AspNetCore.Hosting; -internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, ISupportsUseDefaultServiceProvider +internal sealed class GenericWebHostBuilder : WebHostBuilderBase, ISupportsStartup { - private readonly IHostBuilder _builder; - private readonly IConfiguration _config; private object? _startupObject; private readonly object _startupKey = new object(); @@ -29,18 +27,8 @@ internal sealed class GenericWebHostBuilder : IWebHostBuilder, ISupportsStartup, private HostingStartupWebHostBuilder? _hostingStartupWebHostBuilder; public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) + : base(builder, options) { - _builder = builder; - var configBuilder = new ConfigurationBuilder() - .AddInMemoryCollection(); - - if (!options.SuppressEnvironmentConfiguration) - { - configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); - } - - _config = configBuilder.Build(); - _builder.ConfigureHostConfiguration(config => { config.AddConfiguration(_config); @@ -172,51 +160,6 @@ private void ExecuteHostingStartups() } } - public IWebHost Build() - { - throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); - } - - public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) - { - _builder.ConfigureAppConfiguration((context, builder) => - { - var webhostBuilderContext = GetWebHostBuilderContext(context); - configureDelegate(webhostBuilderContext, builder); - }); - - return this; - } - - public IWebHostBuilder ConfigureServices(Action configureServices) - { - return ConfigureServices((context, services) => configureServices(services)); - } - - public IWebHostBuilder ConfigureServices(Action configureServices) - { - _builder.ConfigureServices((context, builder) => - { - var webhostBuilderContext = GetWebHostBuilderContext(context); - configureServices(webhostBuilderContext, builder); - }); - - return this; - } - - public IWebHostBuilder UseDefaultServiceProvider(Action configure) - { - _builder.UseServiceProviderFactory(context => - { - var webHostBuilderContext = GetWebHostBuilderContext(context); - var options = new ServiceProviderOptions(); - configure(webHostBuilderContext, options); - return new DefaultServiceProviderFactory(options); - }); - - return this; - } - public IWebHostBuilder UseStartup([DynamicallyAccessedMembers(StartupLinkerOptions.Accessibility)] Type startupType) { var startupAssemblyName = startupType.Assembly.GetName().Name; @@ -412,40 +355,6 @@ public IWebHostBuilder Configure(Action { config.AddConfiguration(_config); @@ -69,54 +56,6 @@ public SlimWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options) }); } - public IWebHost Build() - { - throw new NotSupportedException($"Building this implementation of {nameof(IWebHostBuilder)} is not supported."); - } - - public IWebHostBuilder ConfigureAppConfiguration(Action configureDelegate) - { - _builder.ConfigureAppConfiguration((context, builder) => - { - var webhostBuilderContext = GetWebHostBuilderContext(context); - configureDelegate(webhostBuilderContext, builder); - }); - - return this; - } - - public IWebHostBuilder ConfigureServices(Action configureServices) - { - return ConfigureServices((context, services) => configureServices(services)); - } - - public IWebHostBuilder ConfigureServices(Action configureServices) - { - _builder.ConfigureServices((context, builder) => - { - var webhostBuilderContext = GetWebHostBuilderContext(context); - configureServices(webhostBuilderContext, builder); - }); - - return this; - } - - public IWebHostBuilder UseDefaultServiceProvider(Action configure) - { - _builder.UseServiceProviderFactory(context => - { - var webHostBuilderContext = GetWebHostBuilderContext(context); - var options = new ServiceProviderOptions(); - configure(webHostBuilderContext, options); - // TODO: Remove when DI no longer has RequiresDynamicCodeAttribute https://github.com/dotnet/runtime/pull/79425 -#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. - return new DefaultServiceProviderFactory(options); -#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling. - }); - - return this; - } - public IWebHostBuilder Configure(Action configure) { throw new NotSupportedException(); @@ -149,38 +88,4 @@ public IWebHostBuilder Configure(Action configureDelegate) + { + _builder.ConfigureAppConfiguration((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureDelegate(webhostBuilderContext, builder); + }); + + return this; + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + return ConfigureServices((context, services) => configureServices(services)); + } + + public IWebHostBuilder ConfigureServices(Action configureServices) + { + _builder.ConfigureServices((context, builder) => + { + var webhostBuilderContext = GetWebHostBuilderContext(context); + configureServices(webhostBuilderContext, builder); + }); + + return this; + } + + public IWebHostBuilder UseDefaultServiceProvider(Action configure) + { + _builder.UseServiceProviderFactory(context => + { + var webHostBuilderContext = GetWebHostBuilderContext(context); + var options = new ServiceProviderOptions(); + configure(webHostBuilderContext, options); + return new DefaultServiceProviderFactory(options); + }); + + return this; + } + + protected WebHostBuilderContext GetWebHostBuilderContext(HostBuilderContext context) + { + if (!context.Properties.TryGetValue(typeof(WebHostBuilderContext), out var contextVal)) + { + // Use _config as a fallback for WebHostOptions in case the chained source was removed from the hosting IConfigurationBuilder. + var options = new WebHostOptions(context.Configuration, fallbackConfiguration: _config, environment: context.HostingEnvironment); + var webHostBuilderContext = new WebHostBuilderContext + { + Configuration = context.Configuration, + HostingEnvironment = new HostingEnvironment(), + }; + webHostBuilderContext.HostingEnvironment.Initialize(context.HostingEnvironment.ContentRootPath, options, baseEnvironment: context.HostingEnvironment); + context.Properties[typeof(WebHostBuilderContext)] = webHostBuilderContext; + context.Properties[typeof(WebHostOptions)] = options; + return webHostBuilderContext; + } + + // Refresh config, it's periodically updated/replaced + var webHostContext = (WebHostBuilderContext)contextVal; + webHostContext.Configuration = context.Configuration; + return webHostContext; + } + + public string? GetSetting(string key) + { + return _config[key]; + } + + public IWebHostBuilder UseSetting(string key, string? value) + { + _config[key] = value; + return this; + } +} diff --git a/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs index f874d54f3995..ea37f73aa036 100644 --- a/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs +++ b/src/Hosting/Hosting/src/GenericHostWebHostBuilderExtensions.cs @@ -36,7 +36,7 @@ public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action new GenericWebHostBuilder(hostBuilder, options), + static (hostBuilder, options) => new GenericWebHostBuilder(hostBuilder, options), configure, configureWebHostBuilder); } @@ -52,7 +52,7 @@ public static IHostBuilder ConfigureSlimWebHost(this IHostBuilder builder, Actio { return ConfigureWebHost( builder, - (hostBuilder, options) => new SlimWebHostBuilder(hostBuilder, options), + static (hostBuilder, options) => new SlimWebHostBuilder(hostBuilder, options), configure, configureWebHostBuilder); } From 2073eeb4e7ebb2849e79267023fb7bd8d26113df Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 18 Jan 2023 18:29:15 -0600 Subject: [PATCH 4/7] PR feedback. --- src/DefaultBuilder/src/WebApplicationBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index c911651c45ee..eab1eb2b7bb1 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -94,6 +94,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings { + DisableDefaults = true, Args = options.Args, ApplicationName = options.ApplicationName, EnvironmentName = options.EnvironmentName, From 2216e6edb77eb30b06e4790de55425e72134838c Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 19 Jan 2023 18:12:48 -0600 Subject: [PATCH 5/7] Revert DisableDefaults changes. It is breaking too much functionality that will need to be reimplemented from the Extensions.HostBuilder. --- src/DefaultBuilder/src/WebApplicationBuilder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index eab1eb2b7bb1..c911651c45ee 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -94,7 +94,6 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings { - DisableDefaults = true, Args = options.Args, ApplicationName = options.ApplicationName, EnvironmentName = options.EnvironmentName, From 0a58daacc0ae6032b63e9b6468674b7ecd57d6d9 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 19 Jan 2023 22:01:08 -0600 Subject: [PATCH 6/7] Add back DisableDefaults = true with passing tests. --- .../src/WebApplicationBuilder.cs | 28 +++++++++++++++ .../WebApplicationTests.cs | 36 ++++++++++++++++--- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index c911651c45ee..344c4480ca67 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -92,8 +92,17 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< var configuration = new ConfigurationManager(); + // add the default host configuration sources, so they are picked up by the HostApplicationBuilder constructor. + // These won't be added by HostApplicationBuilder since DisableDefaults = true. + configuration.AddEnvironmentVariables(prefix: "DOTNET_"); + if (options.Args is { Length: > 0 } args) + { + configuration.AddCommandLine(args); + } + _hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings { + DisableDefaults = true, Args = options.Args, ApplicationName = options.ApplicationName, EnvironmentName = options.EnvironmentName, @@ -101,6 +110,10 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< Configuration = configuration, }); + // configure the ServiceProvider explicitly because DisableDefaults means HostApplicationBuilder won't. + var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder); + _hostApplicationBuilder.ConfigureContainer(serviceProviderFactory); + // Set WebRootPath if necessary if (options.WebRootPath is not null) { @@ -147,6 +160,21 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services); } + private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder) + { + if (hostApplicationBuilder.Environment.IsDevelopment()) + { + return new DefaultServiceProviderFactory( + new ServiceProviderOptions + { + ValidateScopes = true, + ValidateOnBuild = true, + }); + } + + return new DefaultServiceProviderFactory(); + } + /// /// Provides information about the web hosting environment an application is running. /// diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs index 9d94502ca80f..104e13788f2c 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs @@ -1505,14 +1505,33 @@ public async Task CanAddMiddlewareBeforeUseRouting(CreateBuilderFunc createBuild Assert.Equal("One", chosenEndpoint); } + public static IEnumerable OnlyAddsDefaultServicesOnceData + { + get + { + // The slim builder doesn't add logging services by default + yield return new object[] { (CreateBuilderFunc)CreateBuilder, true }; + yield return new object[] { (CreateBuilderFunc)CreateSlimBuilder, false }; + } + } + [Theory] - [MemberData(nameof(CreateBuilderFuncs))] - public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder) + [MemberData(nameof(OnlyAddsDefaultServicesOnceData))] + public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder, bool hasLogging) { var builder = createBuilder(); // IWebHostEnvironment is added by ConfigureDefaults - Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions))); + var loggingDescriptors = builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions)); + if (hasLogging) + { + Assert.Single(loggingDescriptors); + } + else + { + Assert.Empty(loggingDescriptors); + } + // IWebHostEnvironment is added by ConfigureWebHostDefaults Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IWebHostEnvironment))); Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IOptionsChangeTokenSource))); @@ -1520,7 +1539,16 @@ public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilde await using var app = builder.Build(); - Assert.Single(app.Services.GetRequiredService>>()); + var loggingServices = app.Services.GetRequiredService>>(); + if (hasLogging) + { + Assert.Single(loggingServices); + } + else + { + Assert.Empty(loggingServices); + } + Assert.Single(app.Services.GetRequiredService>()); Assert.Single(app.Services.GetRequiredService>>()); Assert.Single(app.Services.GetRequiredService>()); From 1df71850ea1e5934a8d550914fec5b0bcd06af0b Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Fri, 20 Jan 2023 17:41:24 -0600 Subject: [PATCH 7/7] PR feedback Explicitly add ASPNETCORE environment variables. --- src/DefaultBuilder/src/WebApplicationBuilder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 344c4480ca67..0f7bca59694c 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -92,6 +92,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< var configuration = new ConfigurationManager(); + configuration.AddEnvironmentVariables(prefix: "ASPNETCORE_"); + // add the default host configuration sources, so they are picked up by the HostApplicationBuilder constructor. // These won't be added by HostApplicationBuilder since DisableDefaults = true. configuration.AddEnvironmentVariables(prefix: "DOTNET_"); @@ -110,7 +112,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< Configuration = configuration, }); - // configure the ServiceProvider explicitly because DisableDefaults means HostApplicationBuilder won't. + // configure the ServiceProviderOptions here since DisableDefaults = true means HostApplicationBuilder won't. var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder); _hostApplicationBuilder.ConfigureContainer(serviceProviderFactory);