diff --git a/src/DefaultBuilder/src/WebApplicationBuilder.cs b/src/DefaultBuilder/src/WebApplicationBuilder.cs index 8a95266c7b35..8f065b3372ab 100644 --- a/src/DefaultBuilder/src/WebApplicationBuilder.cs +++ b/src/DefaultBuilder/src/WebApplicationBuilder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Reflection; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; @@ -111,7 +112,8 @@ internal WebApplicationBuilder(WebApplicationOptions options, bool slim, Action< }); // Ensure the same behavior of the non-slim WebApplicationBuilder by adding the default "app" Configuration sources - ApplyDefaultAppConfiguration(options, configuration); + ApplyDefaultAppConfigurationSlim(_hostApplicationBuilder.Environment, configuration, options.Args); + AddDefaultServicesSlim(configuration, _hostApplicationBuilder.Services); // configure the ServiceProviderOptions here since CreateEmptyApplicationBuilder won't. var serviceProviderFactory = GetServiceProviderFactory(_hostApplicationBuilder); @@ -204,14 +206,66 @@ private static void SetDefaultContentRoot(WebApplicationOptions options, Configu } } - private static void ApplyDefaultAppConfiguration(WebApplicationOptions options, ConfigurationManager configuration) + private static void ApplyDefaultAppConfigurationSlim(IHostEnvironment env, ConfigurationManager configuration, string[]? args) { + // Logic taken from https://github.com/dotnet/runtime/blob/6149ca07d2202c2d0d518e10568c0d0dd3473576/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L229-L256 + + var reloadOnChange = GetReloadConfigOnChangeValue(configuration); + + configuration.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange); + + if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 }) + { + try + { + var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); + configuration.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange); + } + catch (FileNotFoundException) + { + // The assembly cannot be found, so just skip it. + } + } + configuration.AddEnvironmentVariables(); - if (options.Args is { Length: > 0 } args) + if (args is { Length: > 0 }) { configuration.AddCommandLine(args); } + + static bool GetReloadConfigOnChangeValue(ConfigurationManager configuration) + { + const string reloadConfigOnChangeKey = "hostBuilder:reloadConfigOnChange"; + var result = true; + if (configuration[reloadConfigOnChangeKey] is string reloadConfigOnChange) + { + if (!bool.TryParse(reloadConfigOnChange, out result)) + { + throw new InvalidOperationException($"Failed to convert configuration value at '{configuration.GetSection(reloadConfigOnChangeKey).Path}' to type '{typeof(bool)}'."); + } + } + return result; + } + } + + private static void AddDefaultServicesSlim(ConfigurationManager configuration, IServiceCollection services) + { + // 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 + services.AddLogging(logging => + { + logging.AddConfiguration(configuration.GetSection("Logging")); + logging.AddSimpleConsole(); + + logging.Configure(options => + { + options.ActivityTrackingOptions = + ActivityTrackingOptions.SpanId | + ActivityTrackingOptions.TraceId | + ActivityTrackingOptions.ParentId; + }); + }); } /// diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs index eba3433d5d3d..d734f0a90e7d 100644 --- a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebApplicationTests.cs @@ -24,6 +24,8 @@ using Microsoft.AspNetCore.Tests; using Microsoft.DotNet.RemoteExecutor; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; +using Microsoft.Extensions.Configuration.UserSecrets; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; @@ -31,6 +33,7 @@ using Microsoft.Extensions.Options; [assembly: HostingStartup(typeof(WebApplicationTests.TestHostingStartup))] +[assembly: UserSecretsId("UserSecret-TestId")] namespace Microsoft.AspNetCore.Tests; @@ -609,6 +612,41 @@ public void ContentRootIsBaseDirectoryWhenCurrentIsSpecialFolderSystem() }, options); } + public static IEnumerable EnablesAppSettingsConfigurationData + { + get + { + yield return new object[] { (CreateBuilderOptionsFunc)CreateBuilderOptions, true }; + yield return new object[] { (CreateBuilderOptionsFunc)CreateBuilderOptions, false }; + yield return new object[] { (CreateBuilderOptionsFunc)CreateSlimBuilderOptions, true }; + yield return new object[] { (CreateBuilderOptionsFunc)CreateSlimBuilderOptions, false }; + } + } + + [Theory] + [MemberData(nameof(EnablesAppSettingsConfigurationData))] + public void WebApplicationBuilderEnablesAppSettingsConfiguration(CreateBuilderOptionsFunc createBuilder, bool isDevelopment) + { + var options = new WebApplicationOptions + { + EnvironmentName = isDevelopment ? Environments.Development : Environments.Production + }; + + var webApplication = createBuilder(options).Build(); + + var config = Assert.IsType(webApplication.Configuration); + Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.json"); + + if (isDevelopment) + { + Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.Development.json"); + } + else + { + Assert.DoesNotContain(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "appsettings.Development.json"); + } + } + [Theory] [MemberData(nameof(CreateBuilderOptionsFuncs))] public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemblyLoadForUserSecretsFail(CreateBuilderOptionsFunc createBuilder) @@ -626,6 +664,22 @@ public void WebApplicationBuilderSettingInvalidApplicationDoesNotThrowWhenAssemb Assert.Equal(Environments.Development, webApplication.Environment.EnvironmentName); } + [Theory] + [MemberData(nameof(CreateBuilderOptionsFuncs))] + public void WebApplicationBuilderEnablesUserSecretsInDevelopment(CreateBuilderOptionsFunc createBuilder) + { + var options = new WebApplicationOptions + { + ApplicationName = typeof(WebApplicationTests).Assembly.GetName().Name, + EnvironmentName = Environments.Development + }; + + var webApplication = createBuilder(options).Build(); + + var config = Assert.IsType(webApplication.Configuration); + Assert.Contains(config.Sources, source => source is JsonConfigurationSource jsonSource && jsonSource.Path == "secrets.json"); + } + [Theory] [MemberData(nameof(WebApplicationBuilderConstructorFuncs))] public void WebApplicationBuilderCanConfigureHostSettingsUsingWebApplicationOptions(WebApplicationBuilderConstructorFunc createBuilder) @@ -1470,7 +1524,7 @@ public async Task WebApplication_CanCallUseEndpointsWithoutUseRoutingFails(Creat [Fact] public void WebApplicationCreate_RegistersEventSourceLogger() { - var listener = new TestEventListener(); + using var listener = new TestEventListener(); var app = WebApplication.Create(); var logger = app.Services.GetRequiredService>(); @@ -1487,7 +1541,7 @@ public void WebApplicationCreate_RegistersEventSourceLogger() [MemberData(nameof(CreateBuilderFuncs))] public void WebApplicationBuilder_CanClearDefaultLoggers(CreateBuilderFunc createBuilder) { - var listener = new TestEventListener(); + using var listener = new TestEventListener(); var builder = createBuilder(); builder.Logging.ClearProviders(); @@ -1590,33 +1644,14 @@ public async Task CanAddMiddlewareBeforeUseRouting(CreateBuilderFunc createBuild Assert.Equal("One", chosenEndpoint); } - public static IEnumerable OnlyAddsDefaultServicesOnceData - { - get - { - // The slim builder doesn't add logging services by default - yield return new object[] { (CreateBuilderFunc)CreateBuilder, true }; - yield return new object[] { (CreateBuilderFunc)CreateSlimBuilder, false }; - } - } - [Theory] - [MemberData(nameof(OnlyAddsDefaultServicesOnceData))] - public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder, bool hasLogging) + [MemberData(nameof(CreateBuilderFuncs))] + public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilderFunc createBuilder) { var builder = createBuilder(); // IWebHostEnvironment is added by ConfigureDefaults - var loggingDescriptors = builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions)); - if (hasLogging) - { - Assert.Single(loggingDescriptors); - } - else - { - Assert.Empty(loggingDescriptors); - } - + Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IConfigureOptions))); // IWebHostEnvironment is added by ConfigureWebHostDefaults Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IWebHostEnvironment))); Assert.Single(builder.Services.Where(descriptor => descriptor.ServiceType == typeof(IOptionsChangeTokenSource))); @@ -1624,16 +1659,7 @@ public async Task WebApplicationBuilder_OnlyAddsDefaultServicesOnce(CreateBuilde await using var app = builder.Build(); - var loggingServices = app.Services.GetRequiredService>>(); - if (hasLogging) - { - Assert.Single(loggingServices); - } - else - { - Assert.Empty(loggingServices); - } - + Assert.Single(app.Services.GetRequiredService>>()); Assert.Single(app.Services.GetRequiredService>()); Assert.Single(app.Services.GetRequiredService>>()); Assert.Single(app.Services.GetRequiredService>()); @@ -1867,7 +1893,7 @@ public async Task PropertiesPreservedFromInnerApplication(CreateBuilderFunc crea [Theory] [MemberData(nameof(CreateBuilderOptionsFuncs))] - public async Task DeveloperExceptionPageIsOnByDefaltInDevelopment(CreateBuilderOptionsFunc createBuilder) + public async Task DeveloperExceptionPageIsOnByDefaultInDevelopment(CreateBuilderOptionsFunc createBuilder) { var builder = createBuilder(new WebApplicationOptions() { EnvironmentName = Environments.Development }); builder.WebHost.UseTestServer(); @@ -2331,7 +2357,7 @@ public async Task SupportsDisablingMiddlewareAutoRegistration(CreateBuilderFunc app.Properties["__AuthenticationMiddlewareSet"] = true; - app.MapGet("/hello", (ClaimsPrincipal user) => {}).AllowAnonymous(); + app.MapGet("/hello", (ClaimsPrincipal user) => { }).AllowAnonymous(); Assert.True(app.Properties.ContainsKey("__AuthenticationMiddlewareSet")); Assert.False(app.Properties.ContainsKey("__AuthorizationMiddlewareSet")); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs index 69f7df3c6280..2e7bd1cf7c98 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.Main.cs @@ -9,7 +9,6 @@ public class Program public static void Main(string[] args) { var builder = WebApplication.CreateSlimBuilder(args); - builder.Logging.AddConsole(); #if (NativeAot) builder.Services.ConfigureHttpJsonOptions(options => diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs index 50d640ff22ca..64039cf01e20 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/Program.cs @@ -4,7 +4,6 @@ using Company.ApiApplication1; var builder = WebApplication.CreateSlimBuilder(args); -builder.Logging.AddConsole(); #if (NativeAot) builder.Services.ConfigureHttpJsonOptions(options => diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.Development.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.Development.json new file mode 100644 index 000000000000..0c208ae9181e --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.json b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.json new file mode 100644 index 000000000000..10f68b8c8b4f --- /dev/null +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/Api-CSharp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json index 5ce2881b37bd..431837fad353 100644 --- a/src/ProjectTemplates/test/Templates.Tests/template-baselines.json +++ b/src/ProjectTemplates/test/Templates.Tests/template-baselines.json @@ -532,6 +532,8 @@ "Template": "api", "Arguments": "new api", "Files": [ + "appsettings.Development.json", + "appsettings.json", "Todo.cs", "Program.cs", "{ProjectName}.http", @@ -543,6 +545,8 @@ "Template": "api", "Arguments": "new api -aot", "Files": [ + "appsettings.Development.json", + "appsettings.json", "Todo.cs", "Program.cs", "{ProjectName}.http", @@ -554,6 +558,8 @@ "Template": "api", "Arguments": "new api --use-program-main", "Files": [ + "appsettings.Development.json", + "appsettings.json", "Todo.cs", "Program.cs", "{ProjectName}.http", @@ -565,6 +571,8 @@ "Template": "api", "Arguments": "new api -aot --use-program-main", "Files": [ + "appsettings.Development.json", + "appsettings.json", "Todo.cs", "Program.cs", "{ProjectName}.http",