Skip to content

Commit 0a3a01b

Browse files
authored
Allow minimal host to be created without default HostBuilder behavior (#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 #32485 * Use the new slim hosting API in the api template. Refactor the WebHostBuilder classes to share more code.
1 parent 9ec0753 commit 0a3a01b

File tree

12 files changed

+847
-365
lines changed

12 files changed

+847
-365
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
#nullable enable
2+
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder() -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
3+
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(Microsoft.AspNetCore.Builder.WebApplicationOptions! options) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!
4+
static Microsoft.AspNetCore.Builder.WebApplication.CreateSlimBuilder(string![]! args) -> Microsoft.AspNetCore.Builder.WebApplicationBuilder!

src/DefaultBuilder/src/WebApplication.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ public static WebApplication Create(string[]? args = null) =>
9898
public static WebApplicationBuilder CreateBuilder() =>
9999
new(new());
100100

101+
/// <summary>
102+
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
103+
/// </summary>
104+
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
105+
public static WebApplicationBuilder CreateSlimBuilder() =>
106+
new(new(), slim: true);
107+
101108
/// <summary>
102109
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
103110
/// </summary>
@@ -106,6 +113,14 @@ public static WebApplicationBuilder CreateBuilder() =>
106113
public static WebApplicationBuilder CreateBuilder(string[] args) =>
107114
new(new() { Args = args });
108115

116+
/// <summary>
117+
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
118+
/// </summary>
119+
/// <param name="args">Command line arguments</param>
120+
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
121+
public static WebApplicationBuilder CreateSlimBuilder(string[] args) =>
122+
new(new() { Args = args }, slim: true);
123+
109124
/// <summary>
110125
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
111126
/// </summary>
@@ -114,6 +129,14 @@ public static WebApplicationBuilder CreateBuilder(string[] args) =>
114129
public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) =>
115130
new(options);
116131

132+
/// <summary>
133+
/// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with minimal defaults.
134+
/// </summary>
135+
/// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param>
136+
/// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
137+
public static WebApplicationBuilder CreateSlimBuilder(WebApplicationOptions options) =>
138+
new(options, slim: true);
139+
117140
/// <summary>
118141
/// Start the application.
119142
/// </summary>

src/DefaultBuilder/src/WebApplicationBuilder.cs

Lines changed: 132 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,97 @@ internal WebApplicationBuilder(WebApplicationOptions options, Action<IHostBuilde
8686
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
8787
}
8888

89+
internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<IHostBuilder>? configureDefaults = null)
90+
{
91+
Debug.Assert(slim, "should only be called with slim: true");
92+
93+
var configuration = new ConfigurationManager();
94+
95+
configuration.AddEnvironmentVariables(prefix: "ASPNETCORE_");
96+
97+
// add the default host configuration sources, so they are picked up by the HostApplicationBuilder constructor.
98+
// These won't be added by HostApplicationBuilder since DisableDefaults = true.
99+
configuration.AddEnvironmentVariables(prefix: "DOTNET_");
100+
if (options.Args is { Length: > 0 } args)
101+
{
102+
configuration.AddCommandLine(args);
103+
}
104+
105+
_hostApplicationBuilder = new HostApplicationBuilder(new HostApplicationBuilderSettings
106+
{
107+
DisableDefaults = true,
108+
Args = options.Args,
109+
ApplicationName = options.ApplicationName,
110+
EnvironmentName = options.EnvironmentName,
111+
ContentRootPath = options.ContentRootPath,
112+
Configuration = configuration,
113+
});
114+
115+
// configure the ServiceProviderOptions here since DisableDefaults = true means HostApplicationBuilder won't.
116+
var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder);
117+
_hostApplicationBuilder.ConfigureContainer(serviceProviderFactory);
118+
119+
// Set WebRootPath if necessary
120+
if (options.WebRootPath is not null)
121+
{
122+
Configuration.AddInMemoryCollection(new[]
123+
{
124+
new KeyValuePair<string, string?>(WebHostDefaults.WebRootKey, options.WebRootPath),
125+
});
126+
}
127+
128+
// Run methods to configure web host defaults early to populate services
129+
var bootstrapHostBuilder = new BootstrapHostBuilder(_hostApplicationBuilder);
130+
131+
// This is for testing purposes
132+
configureDefaults?.Invoke(bootstrapHostBuilder);
133+
134+
bootstrapHostBuilder.ConfigureSlimWebHost(
135+
webHostBuilder =>
136+
{
137+
AspNetCore.WebHost.UseKestrel(webHostBuilder);
138+
139+
webHostBuilder.Configure(ConfigureEmptyApplication);
140+
141+
webHostBuilder.UseSetting(WebHostDefaults.ApplicationKey, _hostApplicationBuilder.Environment.ApplicationName ?? "");
142+
webHostBuilder.UseSetting(WebHostDefaults.PreventHostingStartupKey, Configuration[WebHostDefaults.PreventHostingStartupKey]);
143+
webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, Configuration[WebHostDefaults.HostingStartupAssembliesKey]);
144+
webHostBuilder.UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, Configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);
145+
},
146+
options =>
147+
{
148+
// We've already applied "ASPNETCORE_" environment variables to hosting config
149+
options.SuppressEnvironmentConfiguration = true;
150+
});
151+
152+
// This applies the config from ConfigureWebHostDefaults
153+
// Grab the GenericWebHostService ServiceDescriptor so we can append it after any user-added IHostedServices during Build();
154+
_genericWebHostServiceDescriptor = bootstrapHostBuilder.RunDefaultCallbacks();
155+
156+
// Grab the WebHostBuilderContext from the property bag to use in the ConfigureWebHostBuilder. Then
157+
// grab the IWebHostEnvironment from the webHostContext. This also matches the instance in the IServiceCollection.
158+
var webHostContext = (WebHostBuilderContext)bootstrapHostBuilder.Properties[typeof(WebHostBuilderContext)];
159+
Environment = webHostContext.HostingEnvironment;
160+
161+
Host = new ConfigureHostBuilder(bootstrapHostBuilder.Context, Configuration, Services);
162+
WebHost = new ConfigureWebHostBuilder(webHostContext, Configuration, Services);
163+
}
164+
165+
private static DefaultServiceProviderFactory GetServiceProviderFactory(HostApplicationBuilder hostApplicationBuilder)
166+
{
167+
if (hostApplicationBuilder.Environment.IsDevelopment())
168+
{
169+
return new DefaultServiceProviderFactory(
170+
new ServiceProviderOptions
171+
{
172+
ValidateScopes = true,
173+
ValidateOnBuild = true,
174+
});
175+
}
176+
177+
return new DefaultServiceProviderFactory();
178+
}
179+
89180
/// <summary>
90181
/// Provides information about the web hosting environment an application is running.
91182
/// </summary>
@@ -133,6 +224,46 @@ public WebApplication Build()
133224
}
134225

135226
private void ConfigureApplication(WebHostBuilderContext context, IApplicationBuilder app)
227+
{
228+
ConfigureApplicationCore(
229+
context,
230+
app,
231+
processAuthMiddlewares: () =>
232+
{
233+
Debug.Assert(_builtApplication is not null);
234+
235+
// Process authorization and authentication middlewares independently to avoid
236+
// registering middlewares for services that do not exist
237+
var serviceProviderIsService = _builtApplication.Services.GetService<IServiceProviderIsService>();
238+
if (serviceProviderIsService?.IsService(typeof(IAuthenticationSchemeProvider)) is true)
239+
{
240+
// Don't add more than one instance of the middleware
241+
if (!_builtApplication.Properties.ContainsKey(AuthenticationMiddlewareSetKey))
242+
{
243+
// The Use invocations will set the property on the outer pipeline,
244+
// but we want to set it on the inner pipeline as well.
245+
_builtApplication.Properties[AuthenticationMiddlewareSetKey] = true;
246+
app.UseAuthentication();
247+
}
248+
}
249+
250+
if (serviceProviderIsService?.IsService(typeof(IAuthorizationHandlerProvider)) is true)
251+
{
252+
if (!_builtApplication.Properties.ContainsKey(AuthorizationMiddlewareSetKey))
253+
{
254+
_builtApplication.Properties[AuthorizationMiddlewareSetKey] = true;
255+
app.UseAuthorization();
256+
}
257+
}
258+
});
259+
}
260+
261+
private void ConfigureEmptyApplication(WebHostBuilderContext context, IApplicationBuilder app)
262+
{
263+
ConfigureApplicationCore(context, app, processAuthMiddlewares: null);
264+
}
265+
266+
private void ConfigureApplicationCore(WebHostBuilderContext context, IApplicationBuilder app, Action? processAuthMiddlewares)
136267
{
137268
Debug.Assert(_builtApplication is not null);
138269

@@ -173,29 +304,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
173304
}
174305
}
175306

176-
// Process authorization and authentication middlewares independently to avoid
177-
// registering middlewares for services that do not exist
178-
var serviceProviderIsService = _builtApplication.Services.GetService<IServiceProviderIsService>();
179-
if (serviceProviderIsService?.IsService(typeof(IAuthenticationSchemeProvider)) is true)
180-
{
181-
// Don't add more than one instance of the middleware
182-
if (!_builtApplication.Properties.ContainsKey(AuthenticationMiddlewareSetKey))
183-
{
184-
// The Use invocations will set the property on the outer pipeline,
185-
// but we want to set it on the inner pipeline as well.
186-
_builtApplication.Properties[AuthenticationMiddlewareSetKey] = true;
187-
app.UseAuthentication();
188-
}
189-
}
190-
191-
if (serviceProviderIsService?.IsService(typeof(IAuthorizationHandlerProvider)) is true)
192-
{
193-
if (!_builtApplication.Properties.ContainsKey(AuthorizationMiddlewareSetKey))
194-
{
195-
_builtApplication.Properties[AuthorizationMiddlewareSetKey] = true;
196-
app.UseAuthorization();
197-
}
198-
}
307+
processAuthMiddlewares?.Invoke();
199308

200309
// Wire the source pipeline to run in the destination pipeline
201310
app.Use(next =>

src/DefaultBuilder/src/WebHost.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
222222
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
223223
}
224224
});
225+
226+
UseKestrel(builder);
227+
228+
builder
229+
.UseIIS()
230+
.UseIISIntegration();
231+
}
232+
233+
internal static void UseKestrel(IWebHostBuilder builder)
234+
{
225235
builder.UseKestrel((builderContext, options) =>
226236
{
227237
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
@@ -248,9 +258,7 @@ internal static void ConfigureWebDefaults(IWebHostBuilder builder)
248258
services.AddTransient<IConfigureOptions<ForwardedHeadersOptions>, ForwardedHeadersOptionsSetup>();
249259

250260
services.AddRouting();
251-
})
252-
.UseIIS()
253-
.UseIISIntegration();
261+
});
254262
}
255263

256264
/// <summary>

0 commit comments

Comments
 (0)