Skip to content

Allow minimal host to be created without default HostBuilder behavior #46040

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/DefaultBuilder/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -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!
23 changes: 23 additions & 0 deletions src/DefaultBuilder/src/WebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ public static WebApplication Create(string[]? args = null) =>
public static WebApplicationBuilder CreateBuilder() =>
new(new());

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
/// </summary>
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
public static WebApplicationBuilder CreateSlimBuilder() =>
new(new(), slim: true);

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
/// </summary>
Expand All @@ -106,6 +113,14 @@ public static WebApplicationBuilder CreateBuilder() =>
public static WebApplicationBuilder CreateBuilder(string[] args) =>
new(new() { Args = args });

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
/// </summary>
/// <param name="args">Command line arguments</param>
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
public static WebApplicationBuilder CreateSlimBuilder(string[] args) =>
new(new() { Args = args }, slim: true);

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
/// </summary>
Expand All @@ -114,6 +129,14 @@ public static WebApplicationBuilder CreateBuilder(string[] args) =>
public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) =>
new(options);

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
/// </summary>
/// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param>
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) =>
new(options, slim: true);

/// <summary>
/// Start the application.
/// </summary>
Expand Down
155 changes: 132 additions & 23 deletions src/DefaultBuilder/src/WebApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,97 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
}

internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<IHostBuilder>? configureDefaults = null)
{
Debug.Assert(slim, "should only be called with slim: true");

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_");
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,
ContentRootPath = options.ContentRootPath,
Configuration = configuration,
});

// configure the ServiceProviderOptions here since DisableDefaults = true means HostApplicationBuilder won't.
var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder);
_hostApplicationBuilder.ConfigureContainer(serviceProviderFactory);

// Set WebRootPath if necessary
if (options.WebRootPath is not null)
{
Configuration.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string?>(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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have not already applied "ASPNETCORE_" environment variables to hosting config in this code path. Only "DOTNET_". We should do both. I bet this is why the macOS template test is failing to load the server certificate.

We had an issue a while back with the template tests using expired dev certs on macOS. That was fixed by using "ASPNETCORE_"-prefixed environment variables to set a default cert, so the tests shouldn't rely on the development cert being up to date.

var finalEnvironmentVariables = new Dictionary<string, string>(environmentVariables)
{
["ASPNETCORE_Kestrel__Certificates__Default__Path"] = _developmentCertificate.CertificatePath,
["ASPNETCORE_Kestrel__Certificates__Default__Password"] = _developmentCertificate.CertificatePassword,
};

I'm guessing the dev cert is working as a fallback on the other OS's, but we still have that original issue on macOS when it falls back to the dev cert. @adityamandaleeka I wonder if this is related to our wider macOS dev cert issues.

});

// 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);
}

private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder)
{
if (hostApplicationBuilder.Environment.IsDevelopment())
{
return new DefaultServiceProviderFactory(
new ServiceProviderOptions
{
ValidateScopes = true,
ValidateOnBuild = true,
});
}

return new DefaultServiceProviderFactory();
}

/// <summary>
/// Provides information about the web hosting environment an application is running.
/// </summary>
Expand Down Expand Up @@ -133,6 +224,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<IServiceProviderIsService>();
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);

Expand Down Expand Up @@ -173,29 +304,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<IServiceProviderIsService>();
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 =>
Expand Down
14 changes: 11 additions & 3 deletions src/DefaultBuilder/src/WebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -248,9 +258,7 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ForwardedHeadersOptionsSetup>();

services.AddRouting();
})
.UseIIS()
.UseIISIntegration();
});
}

/// <summary>
Expand Down
Loading