Skip to content

Commit 5cf2861

Browse files
committed
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 MapXXX or registers an EndpointDataSource manually - AuthN/Z, if the app adds the corresponding Auth services - HostFiltering and ForwardedHeaders Fix #48811
1 parent fdfb0d2 commit 5cf2861

File tree

5 files changed

+238
-27
lines changed

5 files changed

+238
-27
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#nullable enable
2+
static Microsoft.AspNetCore.Builder.WebApplication.CreateEmptyBuilder(Microsoft.AspNetCore.Builder.WebApplicationOptions! options) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
23
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder() -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
34
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(Microsoft.AspNetCore.Builder.WebApplicationOptions! options) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
45
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(string![]! args) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!

src/DefaultBuilder/src/WebApplication.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options)
141141
public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) =>
142142
new(options, slim: true);
143143

144+
/// <summary>
145+
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with no defaults.
146+
/// </summary>
147+
/// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param>
148+
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
149+
public static WebApplicationBuilder CreateEmptyBuilder(WebApplicationOptions options) =>
150+
new(options, slim: false, empty: true);
151+
144152
/// <summary>
145153
/// Start the application.
146154
/// </summary>

src/DefaultBuilder/src/WebApplicationBuilder.cs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
56
using System.Reflection;
67
using Microsoft.AspNetCore.Authentication;
78
using Microsoft.AspNetCore.Authorization;
@@ -75,17 +76,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
7576
options.SuppressEnvironmentConfiguration = true;
7677
});
7778

78-
// This applies the config from ConfigureWebHostDefaults
79-
// Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build();
80-
_genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks();
81-
82-
// Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then
83-
// grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
84-
var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)];
85-
Environment = webHostContext.HostingEnvironment;
86-
87-
Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
88-
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
79+
_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
8980
}
9081

9182
internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<IHostBuilder>? configureDefaults = null)
@@ -138,7 +129,7 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
138129
bootstrapHostBuilder.ConfigureSlimWebHost(
139130
webHostBuilder =>
140131
{
141-
AspNetCore.WebHost.ConfigureWebDefaultsCore(webHostBuilder);
132+
AspNetCore.WebHost.ConfigureWebDefaultsSlim(webHostBuilder);
142133

143134
// Runs inline.
144135
webHostBuilder.Configure(ConfigureApplication);
@@ -154,9 +145,74 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
154145
options.SuppressEnvironmentConfiguration = true;
155146
});
156147

148+
_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
149+
}
150+
151+
internal WebApplicationBuilder(WebApplicationOptions options, bool slim, bool empty, Action<IHostBuilder>? configureDefaults = null)
152+
{
153+
Debug.Assert(!slim, "should only be called with slim: false");
154+
Debug.Assert(empty, "should only be called with empty: true");
155+
156+
var configuration = new ConfigurationManager();
157+
158+
// empty builder should still default the ContentRoot as usual. This is the expected behavior for all WebApplicationBuilders.
159+
SetDefaultContentRoot(options, configuration);
160+
161+
_hostApplicationBuilder = Microsoft.Extensions.Hosting.Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings
162+
{
163+
Args = options.Args,
164+
ApplicationName = options.ApplicationName,
165+
EnvironmentName = options.EnvironmentName,
166+
ContentRootPath = options.ContentRootPath,
167+
Configuration = configuration,
168+
});
169+
170+
// Set WebRootPath if necessary
171+
if (options.WebRootPath is not null)
172+
{
173+
Configuration.AddInMemoryCollection(new[]
174+
{
175+
new KeyValuePair<string, string?>(WebHostDefaults.WebRootKey, options.WebRootPath),
176+
});
177+
}
178+
179+
// Run methods to configure web host defaults early to populate services
180+
var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder);
181+
182+
// This is for testing purposes
183+
configureDefaults?.Invoke(bootstrapHostBuilder);
184+
185+
bootstrapHostBuilder.ConfigureSlimWebHost(
186+
webHostBuilder =>
187+
{
188+
AspNetCore.WebHost.ConfigureWebDefaultsEmpty(webHostBuilder);
189+
190+
// Runs inline.
191+
webHostBuilder.Configure((context, app) => ConfigureApplication(context, app, allowDeveloperExceptionPage: false));
192+
193+
webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? "");
194+
195+
// NOTE: There is no way to add Configuration before this gets called, so it is unnecessary
196+
// to set the following properties from the Configuration:
197+
// - WebHostDefaults.PreventHostingStartupKey
198+
// - WebHostDefaults.HostingStartupAssembliesKey
199+
// - WebHostDefaults.HostingStartupExcludeAssembliesKey
200+
},
201+
options =>
202+
{
203+
// This is an "empty" builder, so don't add the "ASPNETCORE_" environment variables
204+
options.SuppressEnvironmentConfiguration = true;
205+
});
206+
207+
_genericWebHostServiceDescriptor = InitializeHosting(bootstrapHostBuilder);
208+
}
209+
210+
[MemberNotNull(nameof(Environment), nameof(Host), nameof(WebHost))]
211+
private ServiceDescriptor InitializeHosting(BootstrapHostBuilder bootstrapHostBuilder)
212+
{
157213
// This applies the config from ConfigureWebHostDefaults
158214
// Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build();
159-
_genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks();
215+
var genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks();
160216

161217
// Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then
162218
// grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
@@ -165,6 +221,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
165221

166222
Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
167223
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
224+
225+
return genericWebHostServiceDescriptor;
168226
}
169227

170228
private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder)
@@ -272,7 +330,7 @@ private static void AddDefaultServicesSlim(ConfigurationManager configuration, I
272330
/// <summary>
273331
/// Provides information about the web hosting environment an application is running.
274332
/// </summary>
275-
public IWebHostEnvironment Environment { get; }
333+
public IWebHostEnvironment Environment { get; private set; }
276334

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

298356
/// <summary>
299357
/// An <see cref="IHostBuilder"/> for configuring host specific properties, but not building.
300358
/// To build after configuration, call <see cref="Build"/>.
301359
/// </summary>
302-
public ConfigureHostBuilder Host { get; }
360+
public ConfigureHostBuilder Host { get; private set; }
303361

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

@@ -321,7 +379,10 @@ public WebApplication Build()
321379
return _builtApplication;
322380
}
323381

324-
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
382+
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app) =>
383+
ConfigureApplication(context, app, allowDeveloperExceptionPage: true);
384+
385+
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app, bool allowDeveloperExceptionPage)
325386
{
326387
Debug.Assert(_builtApplication is not null);
327388

@@ -332,7 +393,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
332393
app.Properties.Remove(EndpointRouteBuilderKey);
333394
}
334395

335-
if (context.HostingEnvironment.IsDevelopment())
396+
if (allowDeveloperExceptionPage && context.HostingEnvironment.IsDevelopment())
336397
{
337398
app.UseDeveloperExceptionPage();
338399
}

src/DefaultBuilder/src/WebHost.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,19 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
236236
.UseIISIntegration();
237237
}
238238

239-
internal static void ConfigureWebDefaultsCore(IWebHostBuilder builder)
239+
internal static void ConfigureWebDefaultsSlim(IWebHostBuilder builder)
240240
{
241241
ConfigureWebDefaultsWorker(builder.UseKestrelCore().ConfigureKestrel(ConfigureKestrel), configureRouting: null);
242242
}
243243

244+
internal static void ConfigureWebDefaultsEmpty(IWebHostBuilder builder)
245+
{
246+
// Note this doesn't configure any server - Kestrel or otherwise.
247+
// It is "empty" and up to the caller to configure a server.
248+
249+
ConfigureWebDefaultsWorker(builder, configureRouting: null);
250+
}
251+
244252
private static void ConfigureKestrel(WebHostBuilderContext builderContext, KestrelServerOptions options)
245253
{
246254
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);

0 commit comments

Comments
 (0)