Skip to content

Commit

Permalink
Add WebApplication.CreateEmptyBuilder (#49246)
Browse files Browse the repository at this point in the history
* Add WebApplication.CreateEmptyBuilder

Allow an empty WebApplicationBuilder to be created without default behavior. There is no default configuration sources (ex. environment variables or appsettings.json files), no logging, and no web server (ex. Kestrel) configured by default. These can all be added explicitly by the app.

The following middleware can be enabled by the app:

- Routing and Endpoints, if the app calls Services.AddRouting, and then MapXXX or registers an EndpointDataSource manually
- AuthN/Z, if the app adds the corresponding Auth services

Fix #48811

* Remove Routing from the "Empty" builder. It is up to the app to configure what they want. This is the Empty WebApplication.

* Respect hosting startup configuration. Refactor to share logic.
  • Loading branch information
eerhardt authored Jul 13, 2023
1 parent bec0339 commit bad8559
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 37 deletions.
1 change: 1 addition & 0 deletions src/DefaultBuilder/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
static Microsoft.AspNetCore.Builder.WebApplication.CreateEmptyBuilder(Microsoft.AspNetCore.Builder.WebApplicationOptions! options) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
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!
8 changes: 8 additions & 0 deletions src/DefaultBuilder/src/WebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options)
public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) =>
new(options, slim: true);

/// <summary>
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with no 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 CreateEmptyBuilder(WebApplicationOptions options) =>
new(options, slim: false, empty: true);

/// <summary>
/// Start the application.
/// </summary>
Expand Down
111 changes: 85 additions & 26 deletions src/DefaultBuilder/src/WebApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Authentication;
using Microsoft.AspNetCore.Authorization;
Expand Down Expand Up @@ -64,28 +65,15 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
// Runs inline.
webHostBuilder.Configure(ConfigureApplication);

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]);
InitializeWebHostSettings(webHostBuilder);
},
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);
_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
}

internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<IHostBuilder>? configureDefaults = null)
Expand Down Expand Up @@ -138,25 +126,83 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
bootstrapHostBuilder.ConfigureSlimWebHost(
webHostBuilder =>
{
AspNetCore.WebHost.ConfigureWebDefaultsCore(webHostBuilder);
AspNetCore.WebHost.ConfigureWebDefaultsSlim(webHostBuilder);

// Runs inline.
webHostBuilder.Configure(ConfigureApplication);

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]);
InitializeWebHostSettings(webHostBuilder);
},
options =>
{
// We've already applied "ASPNETCORE_" environment variables to hosting config
options.SuppressEnvironmentConfiguration = true;
});

_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
}

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

var configuration = new ConfigurationManager();

// empty builder should still default the ContentRoot as usual. This is the expected behavior for all WebApplicationBuilders.
SetDefaultContentRoot(options, configuration);

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

// 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 =>
{
// Note this doesn't configure any WebHost server - Kestrel or otherwise.
// It also doesn't register Routing, HostFiltering, or ForwardedHeaders.
// It is "empty" and up to the caller to configure these services.

// Runs inline.
webHostBuilder.Configure((context, app) => ConfigureApplication(context, app, allowDeveloperExceptionPage: false));

InitializeWebHostSettings(webHostBuilder);
},
options =>
{
// This is an "empty" builder, so don't add the "ASPNETCORE_" environment variables
options.SuppressEnvironmentConfiguration = true;
});

_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
}

[MemberNotNull(nameof(Environment), nameof(Host), nameof(WebHost))]
private ServiceDescriptor InitializeHosting(BootstrapHostBuilder bootstrapHostBuilder)
{
// 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();
var 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.
Expand All @@ -165,6 +211,16 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<

Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);

return genericWebHostServiceDescriptor;
}

private void InitializeWebHostSettings(IWebHostBuilder webHostBuilder)
{
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]);
}

private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder)
Expand Down Expand Up @@ -272,7 +328,7 @@ private static void AddDefaultServicesSlim(ConfigurationManager configuration, I
/// <summary>
/// Provides information about the web hosting environment an application is running.
/// </summary>
public IWebHostEnvironment Environment { get; }
public IWebHostEnvironment Environment { get; private set; }

/// <summary>
/// A collection of services for the application to compose. This is useful for adding user provided or framework provided services.
Expand All @@ -293,13 +349,13 @@ private static void AddDefaultServicesSlim(ConfigurationManager configuration, I
/// An <see cref="IWebHostBuilder"/> for configuring server specific properties, but not building.
/// To build after configuration, call <see cref="Build"/>.
/// </summary>
public ConfigureWebHostBuilder WebHost { get; }
public ConfigureWebHostBuilder WebHost { get; private set; }

/// <summary>
/// An <see cref="IHostBuilder"/> for configuring host specific properties, but not building.
/// To build after configuration, call <see cref="Build"/>.
/// </summary>
public ConfigureHostBuilder Host { get; }
public ConfigureHostBuilder Host { get; private set; }

IDictionary<object, object> IHostApplicationBuilder.Properties => ((IHostApplicationBuilder)_hostApplicationBuilder).Properties;

Expand All @@ -321,7 +377,10 @@ public WebApplication Build()
return _builtApplication;
}

private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) =>
ConfigureApplication(context, app, allowDeveloperExceptionPage: true);

private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app, bool allowDeveloperExceptionPage)
{
Debug.Assert(_builtApplication is not null);

Expand All @@ -332,7 +391,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
app.Properties.Remove(EndpointRouteBuilderKey);
}

if (context.HostingEnvironment.IsDevelopment())
if (allowDeveloperExceptionPage && context.HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Expand Down
2 changes: 1 addition & 1 deletion src/DefaultBuilder/src/WebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
.UseIISIntegration();
}

internal static void ConfigureWebDefaultsCore(IWebHostBuilder builder)
internal static void ConfigureWebDefaultsSlim(IWebHostBuilder builder)
{
ConfigureWebDefaultsWorker(builder.UseKestrelCore().ConfigureKestrel(ConfigureKestrel), configureRouting: null);
}
Expand Down
Loading

0 comments on commit bad8559

Please sign in to comment.