Skip to content

Commit

Permalink
Add WebApplication.CreateEmptyBuilder
Browse files Browse the repository at this point in the history
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 MapXXX or registers an EndpointDataSource manually
- AuthN/Z, if the app adds the corresponding Auth services
- HostFiltering and ForwardedHeaders

Fix dotnet#48811
  • Loading branch information
eerhardt committed Jul 6, 2023
1 parent fdfb0d2 commit 5cf2861
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 27 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
97 changes: 79 additions & 18 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 @@ -75,17 +76,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
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,7 +129,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
bootstrapHostBuilder.ConfigureSlimWebHost(
webHostBuilder =>
{
AspNetCore.WebHost.ConfigureWebDefaultsCore(webHostBuilder);
AspNetCore.WebHost.ConfigureWebDefaultsSlim(webHostBuilder);

// Runs inline.
webHostBuilder.Configure(ConfigureApplication);
Expand All @@ -154,9 +145,74 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
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 =>
{
AspNetCore.WebHost.ConfigureWebDefaultsEmpty(webHostBuilder);

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

webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? "");

// NOTE: There is no way to add Configuration before this gets called, so it is unnecessary
// to set the following properties from the Configuration:
// - WebHostDefaults.PreventHostingStartupKey
// - WebHostDefaults.HostingStartupAssembliesKey
// - WebHostDefaults.HostingStartupExcludeAssembliesKey
},
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 +221,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<

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

return genericWebHostServiceDescriptor;
}

private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder)
Expand Down Expand Up @@ -272,7 +330,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 +351,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 +379,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 +393,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
app.Properties.Remove(EndpointRouteBuilderKey);
}

if (context.HostingEnvironment.IsDevelopment())
if (allowDeveloperExceptionPage && context.HostingEnvironment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
Expand Down
10 changes: 9 additions & 1 deletion src/DefaultBuilder/src/WebHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,11 +236,19 @@ 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);
}

internal static void ConfigureWebDefaultsEmpty(IWebHostBuilder builder)
{
// Note this doesn't configure any server - Kestrel or otherwise.
// It is "empty" and up to the caller to configure a server.

ConfigureWebDefaultsWorker(builder, configureRouting: null);
}

private static void ConfigureKestrel(WebHostBuilderContext builderContext, KestrelServerOptions options)
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
Expand Down
Loading

0 comments on commit 5cf2861

Please sign in to comment.