Skip to content

Commit 3cb23e1

Browse files
authored
Add default configuration and logging to CreateSlimBuilder (#47797)
* Add default configuration and logging to CreateSlimBuilder Update slim builder to include: - JSON file configuration for appsettings.json and appsettings.{EnvironmentName}.json - UserSecrets configuration - Console logging - Logging configuration Update api template with appsettings files and remove AddConsole This adds back some app size to the api template app. But it is worth the tradeoff given users expect these things to work out of the box. Fix #47598
1 parent 62f0f80 commit 3cb23e1

File tree

7 files changed

+144
-41
lines changed

7 files changed

+144
-41
lines changed

src/DefaultBuilder/src/WebApplicationBuilder.cs

Lines changed: 57 additions & 3 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.Reflection;
56
using Microsoft.AspNetCore.Authentication;
67
using Microsoft.AspNetCore.Authorization;
78
using Microsoft.AspNetCore.Hosting;
@@ -111,7 +112,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action<
111112
});
112113

113114
// Ensure the same behavior of the non-slim WebApplicationBuilder by adding the default "app" Configuration sources
114-
ApplyDefaultAppConfiguration(options, configuration);
115+
ApplyDefaultAppConfigurationSlim(_hostApplicationBuilder.Environment, configuration, options.Args);
116+
AddDefaultServicesSlim(configuration, _hostApplicationBuilder.Services);
115117

116118
// configure the ServiceProviderOptions here since CreateEmptyApplicationBuilder won't.
117119
var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder);
@@ -204,14 +206,66 @@ private static void SetDefaultContentRoot(WebApplicationOptions options, Configu
204206
}
205207
}
206208

207-
private static void ApplyDefaultAppConfiguration(WebApplicationOptions options, ConfigurationManager configuration)
209+
private static void ApplyDefaultAppConfigurationSlim(IHostEnvironment env, ConfigurationManager configuration, string[]? args)
208210
{
211+
// Logic taken from https://github.com/dotnet/runtime/blob/6149ca07d2202c2d0d518e10568c0d0dd3473576/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L229-L256
212+
213+
var reloadOnChange = GetReloadConfigOnChangeValue(configuration);
214+
215+
configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
216+
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
217+
218+
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
219+
{
220+
try
221+
{
222+
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
223+
configuration.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
224+
}
225+
catch (FileNotFoundException)
226+
{
227+
// The assembly cannot be found, so just skip it.
228+
}
229+
}
230+
209231
configuration.AddEnvironmentVariables();
210232

211-
if (options.Args is { Length: > 0 } args)
233+
if (args is { Length: > 0 })
212234
{
213235
configuration.AddCommandLine(args);
214236
}
237+
238+
static bool GetReloadConfigOnChangeValue(ConfigurationManager configuration)
239+
{
240+
const string reloadConfigOnChangeKey = "hostBuilder:reloadConfigOnChange";
241+
var result = true;
242+
if (configuration[reloadConfigOnChangeKey] is string reloadConfigOnChange)
243+
{
244+
if (!bool.TryParse(reloadConfigOnChange, out result))
245+
{
246+
throw new InvalidOperationException($"Failed to convert configuration value at '{configuration.GetSection(reloadConfigOnChangeKey).Path}' to type '{typeof(bool)}'.");
247+
}
248+
}
249+
return result;
250+
}
251+
}
252+
253+
private static void AddDefaultServicesSlim(ConfigurationManager configuration, IServiceCollection services)
254+
{
255+
// Add the necessary services for the slim WebApplicationBuilder, taken from https://github.com/dotnet/runtime/blob/6149ca07d2202c2d0d518e10568c0d0dd3473576/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L266
256+
services.AddLogging(logging =>
257+
{
258+
logging.AddConfiguration(configuration.GetSection("Logging"));
259+
logging.AddSimpleConsole();
260+
261+
logging.Configure(options =>
262+
{
263+
options.ActivityTrackingOptions =
264+
ActivityTrackingOptions.SpanId |
265+
ActivityTrackingOptions.TraceId |
266+
ActivityTrackingOptions.ParentId;
267+
});
268+
});
215269
}
216270

217271
/// <summary>

src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@
2424
using Microsoft.AspNetCore.Tests;
2525
using Microsoft.DotNet.RemoteExecutor;
2626
using Microsoft.Extensions.Configuration;
27+
using Microsoft.Extensions.Configuration.Json;
28+
using Microsoft.Extensions.Configuration.UserSecrets;
2729
using Microsoft.Extensions.DependencyInjection;
2830
using Microsoft.Extensions.DependencyInjection.Extensions;
2931
using Microsoft.Extensions.Hosting;
3032
using Microsoft.Extensions.Logging;
3133
using Microsoft.Extensions.Options;
3234

3335
[assembly: HostingStartup(typeof(WebApplicationTests.TestHostingStartup))]
36+
[assembly: UserSecretsId("UserSecret-TestId")]
3437

3538
namespace Microsoft.AspNetCore.Tests;
3639

@@ -609,6 +612,41 @@ public void ContentRootIsBaseDirectoryWhenCurrentIsSpecialFolderSystem()
609612
}, options);
610613
}
611614

615+
public static IEnumerable<object[]> EnablesAppSettingsConfigurationData
616+
{
617+
get
618+
{
619+
yield return new object[] { (CreateBuilderOptionsFunc)CreateBuilderOptions, true };
620+
yield return new object[] { (CreateBuilderOptionsFunc)CreateBuilderOptions, false };
621+
yield return new object[] { (CreateBuilderOptionsFunc)CreateSlimBuilderOptions, true };
622+
yield return new object[] { (CreateBuilderOptionsFunc)CreateSlimBuilderOptions, false };
623+
}
624+
}
625+
626+
[Theory]
627+
[MemberData(nameof(EnablesAppSettingsConfigurationData))]
628+
public void WebApplicationBuilderEnablesAppSettingsConfiguration(CreateBuilderOptionsFunc createBuilder, bool isDevelopment)
629+
{
630+
var options = new WebApplicationOptions
631+
{
632+
EnvironmentName = isDevelopment ? Environments.Development : Environments.Production
633+
};
634+
635+
var webApplication = createBuilder(options).Build();
636+
637+
var config = Assert.IsType<ConfigurationManager>(webApplication.Configuration);
638+
Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.json");
639+
640+
if (isDevelopment)
641+
{
642+
Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.Development.json");
643+
}
644+
else
645+
{
646+
Assert.DoesNotContain(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.Development.json");
647+
}
648+
}
649+
612650
[Theory]
613651
[MemberData(nameof(CreateBuilderOptionsFuncs))]
614652
public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemblyLoadForUserSecretsFail(CreateBuilderOptionsFunc createBuilder)
@@ -626,6 +664,22 @@ public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemb
626664
Assert.Equal(Environments.Development, webApplication.Environment.EnvironmentName);
627665
}
628666

667+
[Theory]
668+
[MemberData(nameof(CreateBuilderOptionsFuncs))]
669+
public void WebApplicationBuilderEnablesUserSecretsInDevelopment(CreateBuilderOptionsFunc createBuilder)
670+
{
671+
var options = new WebApplicationOptions
672+
{
673+
ApplicationName = typeof(WebApplicationTests).Assembly.GetName().Name,
674+
EnvironmentName = Environments.Development
675+
};
676+
677+
var webApplication = createBuilder(options).Build();
678+
679+
var config = Assert.IsType<ConfigurationManager>(webApplication.Configuration);
680+
Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "secrets.json");
681+
}
682+
629683
[Theory]
630684
[MemberData(nameof(WebApplicationBuilderConstructorFuncs))]
631685
public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptions(WebApplicationBuilderConstructorFunc createBuilder)
@@ -1470,7 +1524,7 @@ public async Task WebApplication_CanCallUseEndpointsWithoutUseRoutingFails(Creat
14701524
[Fact]
14711525
public void WebApplicationCreate_RegistersEventSourceLogger()
14721526
{
1473-
var listener = new TestEventListener();
1527+
using var listener = new TestEventListener();
14741528
var app = WebApplication.Create();
14751529

14761530
var logger = app.Services.GetRequiredService<ILogger<WebApplicationTests>>();
@@ -1487,7 +1541,7 @@ public void WebApplicationCreate_RegistersEventSourceLogger()
14871541
[MemberData(nameof(CreateBuilderFuncs))]
14881542
public void WebApplicationBuilder_CanClearDefaultLoggers(CreateBuilderFunc createBuilder)
14891543
{
1490-
var listener = new TestEventListener();
1544+
using var listener = new TestEventListener();
14911545
var builder = createBuilder();
14921546
builder.Logging.ClearProviders();
14931547

@@ -1590,50 +1644,22 @@ public async Task CanAddMiddlewareBeforeUseRouting(CreateBuilderFunc createBuild
15901644
Assert.Equal("One", chosenEndpoint);
15911645
}
15921646

1593-
public static IEnumerable<object[]> OnlyAddsDefaultServicesOnceData
1594-
{
1595-
get
1596-
{
1597-
// The slim builder doesn't add logging services by default
1598-
yield return new object[] { (CreateBuilderFunc)CreateBuilder, true };
1599-
yield return new object[] { (CreateBuilderFunc)CreateSlimBuilder, false };
1600-
}
1601-
}
1602-
16031647
[Theory]
1604-
[MemberData(nameof(OnlyAddsDefaultServicesOnceData))]
1605-
public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder, bool hasLogging)
1648+
[MemberData(nameof(CreateBuilderFuncs))]
1649+
public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder)
16061650
{
16071651
var builder = createBuilder();
16081652

16091653
// IWebHostEnvironment is added by ConfigureDefaults
1610-
var loggingDescriptors = builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions<LoggerFactoryOptions>));
1611-
if (hasLogging)
1612-
{
1613-
Assert.Single(loggingDescriptors);
1614-
}
1615-
else
1616-
{
1617-
Assert.Empty(loggingDescriptors);
1618-
}
1619-
1654+
Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions<LoggerFactoryOptions>)));
16201655
// IWebHostEnvironment is added by ConfigureWebHostDefaults
16211656
Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IWebHostEnvironment)));
16221657
Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IOptionsChangeTokenSource<HostFilteringOptions>)));
16231658
Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IServer)));
16241659

16251660
await using var app = builder.Build();
16261661

1627-
var loggingServices = app.Services.GetRequiredService<IEnumerable<IConfigureOptions<LoggerFactoryOptions>>>();
1628-
if (hasLogging)
1629-
{
1630-
Assert.Single(loggingServices);
1631-
}
1632-
else
1633-
{
1634-
Assert.Empty(loggingServices);
1635-
}
1636-
1662+
Assert.Single(app.Services.GetRequiredService<IEnumerable<IConfigureOptions<LoggerFactoryOptions>>>());
16371663
Assert.Single(app.Services.GetRequiredService<IEnumerable<IWebHostEnvironment>>());
16381664
Assert.Single(app.Services.GetRequiredService<IEnumerable<IOptionsChangeTokenSource<HostFilteringOptions>>>());
16391665
Assert.Single(app.Services.GetRequiredService<IEnumerable<IServer>>());
@@ -1867,7 +1893,7 @@ public async Task PropertiesPreservedFromInnerApplication(CreateBuilderFunc crea
18671893

18681894
[Theory]
18691895
[MemberData(nameof(CreateBuilderOptionsFuncs))]
1870-
public async Task DeveloperExceptionPageIsOnByDefaltInDevelopment(CreateBuilderOptionsFunc createBuilder)
1896+
public async Task DeveloperExceptionPageIsOnByDefaultInDevelopment(CreateBuilderOptionsFunc createBuilder)
18711897
{
18721898
var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development });
18731899
builder.WebHost.UseTestServer();
@@ -2331,7 +2357,7 @@ public async Task SupportsDisablingMiddlewareAutoRegistration(CreateBuilderFunc
23312357

23322358
app.Properties["__AuthenticationMiddlewareSet"] = true;
23332359

2334-
app.MapGet("/hello", (ClaimsPrincipal user) => {}).AllowAnonymous();
2360+
app.MapGet("/hello", (ClaimsPrincipal user) => { }).AllowAnonymous();
23352361

23362362
Assert.True(app.Properties.ContainsKey("__AuthenticationMiddlewareSet"));
23372363
Assert.False(app.Properties.ContainsKey("__AuthorizationMiddlewareSet"));

src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ public class Program
99
public static void Main(string[] args)
1010
{
1111
var builder = WebApplication.CreateSlimBuilder(args);
12-
builder.Logging.AddConsole();
1312

1413
#if (NativeAot)
1514
builder.Services.ConfigureHttpJsonOptions(options =>

src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using Company.ApiApplication1;
55

66
var builder = WebApplication.CreateSlimBuilder(args);
7-
builder.Logging.AddConsole();
87

98
#if (NativeAot)
109
builder.Services.ConfigureHttpJsonOptions(options =>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
}
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"Logging": {
3+
"LogLevel": {
4+
"Default": "Information",
5+
"Microsoft.AspNetCore": "Warning"
6+
}
7+
},
8+
"AllowedHosts": "*"
9+
}

src/ProjectTemplates/test/Templates.Tests/template-baselines.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@
532532
"Template": "api",
533533
"Arguments": "new api",
534534
"Files": [
535+
"appsettings.Development.json",
536+
"appsettings.json",
535537
"Todo.cs",
536538
"Program.cs",
537539
"{ProjectName}.http",
@@ -543,6 +545,8 @@
543545
"Template": "api",
544546
"Arguments": "new api -aot",
545547
"Files": [
548+
"appsettings.Development.json",
549+
"appsettings.json",
546550
"Todo.cs",
547551
"Program.cs",
548552
"{ProjectName}.http",
@@ -554,6 +558,8 @@
554558
"Template": "api",
555559
"Arguments": "new api --use-program-main",
556560
"Files": [
561+
"appsettings.Development.json",
562+
"appsettings.json",
557563
"Todo.cs",
558564
"Program.cs",
559565
"{ProjectName}.http",
@@ -565,6 +571,8 @@
565571
"Template": "api",
566572
"Arguments": "new api -aot --use-program-main",
567573
"Files": [
574+
"appsettings.Development.json",
575+
"appsettings.json",
568576
"Todo.cs",
569577
"Program.cs",
570578
"{ProjectName}.http",

0 commit comments

Comments
 (0)