Skip to content

Commit

Permalink
Update CreateSlimBuilder to use Host.CreateEmptyApplicationBuilder. (d…
Browse files Browse the repository at this point in the history
…otnet#46579)

* Update CreateSlimBuilder to use Host.CreateEmptyApplicationBuilder.

Also fix 2 existing bugs:
1. ContentRootPath is set to the app's BaseDirectory, and not CurrentWorkingDirectory. Default to CWD by using the same logic as HostApplicationBuilder.
2. Add un-prefixed environment variables to the configuration, and ensure the configuration order is the same as WebApplication.CreateBuilder.

Fix dotnet#46293
  • Loading branch information
eerhardt authored Feb 16, 2023
1 parent 9bb6ab7 commit e0f2d09
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 14 deletions.
54 changes: 45 additions & 9 deletions src/DefaultBuilder/src/WebApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,25 +94,26 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<

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.
// SetDefaultContentRoot needs to be added between 'ASPNETCORE_' and 'DOTNET_' in order to match behavior of the non-slim WebApplicationBuilder.
SetDefaultContentRoot(options, configuration);

// Add the default host environment variable configuration source.
// This won't be added by CreateEmptyApplicationBuilder.
configuration.AddEnvironmentVariables(prefix: "DOTNET_");
if (options.Args is { Length: > 0 } args)
{
configuration.AddCommandLine(args);
}

_hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings
_hostApplicationBuilder = Microsoft.Extensions.Hosting.Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings
{
DisableDefaults = true,
Args = options.Args,
ApplicationName = options.ApplicationName,
EnvironmentName = options.EnvironmentName,
ContentRootPath = options.ContentRootPath,
Configuration = configuration,
});

// configure the ServiceProviderOptions here since DisableDefaults = true means HostApplicationBuilder won't.
// Ensure the same behavior of the non-slim WebApplicationBuilder by adding the default "app" Configuration sources
ApplyDefaultAppConfiguration(options, configuration);

// configure the ServiceProviderOptions here since CreateEmptyApplicationBuilder won't.
var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder);
_hostApplicationBuilder.ConfigureContainer(serviceProviderFactory);

Expand Down Expand Up @@ -177,6 +178,41 @@ private static DefaultServiceProviderFactory GetServiceProviderFactory(HostAppli
return new DefaultServiceProviderFactory();
}

private static void SetDefaultContentRoot(WebApplicationOptions options, ConfigurationManager configuration)
{
if (options.ContentRootPath is null && configuration[HostDefaults.ContentRootKey] is null)
{
// Logic taken from https://github.com/dotnet/runtime/blob/78ed4438a42acab80541e9bde1910abaa8841db2/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L209-L227

// If we're running anywhere other than C:\Windows\system32, we default to using the CWD for the ContentRoot.
// However, since many things like Windows services and MSIX installers have C:\Windows\system32 as there CWD which is not likely
// to really be the home for things like appsettings.json, we skip changing the ContentRoot in that case. The non-"default" initial
// value for ContentRoot is AppContext.BaseDirectory (e.g. the executable path) which probably makes more sense than the system32.

// In my testing, both Environment.CurrentDirectory and Environment.GetFolderPath(Environment.SpecialFolder.System) return the path without
// any trailing directory separator characters. I'm not even sure the casing can ever be different from these APIs, but I think it makes sense to
// ignore case for Windows path comparisons given the file system is usually (always?) going to be case insensitive for the system path.
string cwd = System.Environment.CurrentDirectory;
if (!OperatingSystem.IsWindows() || !string.Equals(cwd, System.Environment.GetFolderPath(System.Environment.SpecialFolder.System), StringComparison.OrdinalIgnoreCase))
{
configuration.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, cwd),
});
}
}
}

private static void ApplyDefaultAppConfiguration(WebApplicationOptions options, ConfigurationManager configuration)
{
configuration.AddEnvironmentVariables();

if (options.Args is { Length: > 0 } args)
{
configuration.AddCommandLine(args);
}
}

/// <summary>
/// Provides information about the web hosting environment an application is running.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.HostFiltering;
using Microsoft.AspNetCore.Hosting;
Expand All @@ -23,6 +21,7 @@
using Microsoft.AspNetCore.TestHost;
using Microsoft.AspNetCore.Testing;
using Microsoft.AspNetCore.Tests;
using Microsoft.DotNet.RemoteExecutor;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
Expand Down Expand Up @@ -556,9 +555,57 @@ public void SettingContentRootToRelativePathUsesAppContextBaseDirectoryAsPathBas
builder.WebHost.UseContentRoot("");

Assert.Equal(NormalizePath(AppContext.BaseDirectory), NormalizePath(builder.Environment.ContentRootPath));
}

private static string NormalizePath(string unnormalizedPath) =>
Path.TrimEndingDirectorySeparator(Path.GetFullPath(unnormalizedPath));

[ConditionalFact]
[RemoteExecutionSupported]
public void ContentRootIsDefaultedToCurrentDirectory()
{
var tmpDir = Directory.CreateTempSubdirectory();

try
{
var options = new RemoteInvokeOptions();
options.StartInfo.WorkingDirectory = tmpDir.FullName;

using var remoteHandle = RemoteExecutor.Invoke(static () =>
{
foreach (object[] data in CreateBuilderFuncs)
{
var createBuilder = (CreateBuilderFunc)data[0];
var builder = createBuilder();

static string NormalizePath(string unnormalizedPath) =>
Path.TrimEndingDirectorySeparator(Path.GetFullPath(unnormalizedPath));
Assert.Equal(NormalizePath(Environment.CurrentDirectory), NormalizePath(builder.Environment.ContentRootPath));
}
}, options);
}
finally
{
tmpDir.Delete(recursive: true);
}
}

[ConditionalFact]
[OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX)]
[RemoteExecutionSupported]
public void ContentRootIsBaseDirectoryWhenCurrentIsSpecialFolderSystem()
{
var options = new RemoteInvokeOptions();
options.StartInfo.WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.System);

using var remoteHandle = RemoteExecutor.Invoke(static () =>
{
foreach (object[] data in CreateBuilderFuncs)
{
var createBuilder = (CreateBuilderFunc)data[0];
var builder = createBuilder();

Assert.Equal(NormalizePath(AppContext.BaseDirectory), NormalizePath(builder.Environment.ContentRootPath));
}
}, options);
}

[Theory]
Expand Down Expand Up @@ -795,6 +842,43 @@ public void WebApplicationBuilderApplicationNameCanBeOverridden(WebApplicationBu
Assert.Equal(assemblyName, webHostEnv.ApplicationName);
}

[ConditionalFact]
[RemoteExecutionSupported]
public void WebApplicationBuilderConfigurationSourcesOrderedCorrectly()
{
// all WebApplicationBuilders have the following configuration sources ordered highest to lowest priority:
// 1. Command-line arguments
// 2. Non-prefixed environment variables
// 3. DOTNET_-prefixed environment variables
// 4. ASPNETCORE_-prefixed environment variables

var options = new RemoteInvokeOptions();
options.StartInfo.EnvironmentVariables.Add("one", "unprefixed_one");
options.StartInfo.EnvironmentVariables.Add("two", "unprefixed_two");
options.StartInfo.EnvironmentVariables.Add("DOTNET_one", "DOTNET_one");
options.StartInfo.EnvironmentVariables.Add("DOTNET_two", "DOTNET_two");
options.StartInfo.EnvironmentVariables.Add("DOTNET_three", "DOTNET_three");
options.StartInfo.EnvironmentVariables.Add("ASPNETCORE_one", "ASPNETCORE_one");
options.StartInfo.EnvironmentVariables.Add("ASPNETCORE_two", "ASPNETCORE_two");
options.StartInfo.EnvironmentVariables.Add("ASPNETCORE_three", "ASPNETCORE_three");
options.StartInfo.EnvironmentVariables.Add("ASPNETCORE_four", "ASPNETCORE_four");

using var remoteHandle = RemoteExecutor.Invoke(static () =>
{
var args = new[] { "--one=command_line_one" };
foreach (object[] data in CreateBuilderArgsFuncs)
{
var createBuilder = (CreateBuilderArgsFunc)data[0];
var builder = createBuilder(args);

Assert.Equal("command_line_one", builder.Configuration["one"]);
Assert.Equal("unprefixed_two", builder.Configuration["two"]);
Assert.Equal("DOTNET_three", builder.Configuration["three"]);
Assert.Equal("ASPNETCORE_four", builder.Configuration["four"]);
}
}, options);
}

[Theory]
[MemberData(nameof(CreateBuilderArgsFuncs))]
public void WebApplicationBuilderCanFlowCommandLineConfigurationToApplication(CreateBuilderArgsFunc createBuilder)
Expand Down Expand Up @@ -976,7 +1060,7 @@ public async Task WebApplicationCanObserveSourcesClearedInBuild(CreateBuilderFun

[Theory]
[MemberData(nameof(CreateBuilderOptionsFuncs))]
public async Task WebApplicationCanObserveSourcesClearedInConfiguratHostConfiguration(CreateBuilderOptionsFunc createBuilder)
public async Task WebApplicationCanObserveSourcesClearedInHostConfiguration(CreateBuilderOptionsFunc createBuilder)
{
// This mimics what WebApplicationFactory<T> does and runs configure
// services callbacks
Expand Down

0 comments on commit e0f2d09

Please sign in to comment.