Skip to content

Add WebApplication.CreateEmptyBuilder #49246

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 4 commits into from
Jul 13, 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
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)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: We only have two remaining readonly fields. Is it time to make those non readonly (and possibly "lie" about the nullability), give these more logical method names and remove the weird bool args. E.g. InitializeEmpty, InitializeSlim, InitializeFull?

Copy link
Member Author

Choose a reason for hiding this comment

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

Let's consider this in a future change. I'm not excited about the factoring now, but it can easily be changed later without breaking anyone.

{
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