Skip to content

Commit

Permalink
Allow minimal host to be created without default HostBuilder behavior (
Browse files Browse the repository at this point in the history
…dotnet#46040)

* 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 dotnet#32485

* Use the new slim hosting API in the api template.

Refactor the WebHostBuilder classes to share more code.
  • Loading branch information
eerhardt authored Jan 21, 2023
1 parent 9ec0753 commit 0a3a01b
Show file tree
Hide file tree
Showing 12 changed files with 847 additions and 365 deletions.
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;
});

// 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

0 comments on commit 0a3a01b

Please sign in to comment.