Added the `ConfigureDefaults` API, as an extension method of `IHostBuilder` (#50447)
authorDavid Pine <david.pine@microsoft.com>
Wed, 31 Mar 2021 00:20:57 +0000 (19:20 -0500)
committerGitHub <noreply@github.com>
Wed, 31 Mar 2021 00:20:57 +0000 (17:20 -0700)
* Added the ConfigureDefaults API, as an extension method on the IHostBuilder interface. Fix #36003

* Apply suggestions from code review

Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
* Update src/libraries/Microsoft.Extensions.Hosting/src/Host.cs

Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
src/libraries/Microsoft.Extensions.Hosting/ref/Microsoft.Extensions.Hosting.cs
src/libraries/Microsoft.Extensions.Hosting/src/Host.cs
src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs
src/libraries/Microsoft.Extensions.Hosting/tests/UnitTests/HostBuilderTests.cs

index 63c5913..1f92e81 100644 (file)
@@ -39,6 +39,7 @@ namespace Microsoft.Extensions.Hosting
     {
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureAppConfiguration(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureContainer<TContainerBuilder>(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<TContainerBuilder> configureDelegate) { throw null; }
+        public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureDefaults(this Microsoft.Extensions.Hosting.IHostBuilder builder, string[] args) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureLogging(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Hosting.HostBuilderContext, Microsoft.Extensions.Logging.ILoggingBuilder> configureLogging) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureLogging(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.Logging.ILoggingBuilder> configureLogging) { throw null; }
         public static Microsoft.Extensions.Hosting.IHostBuilder ConfigureServices(this Microsoft.Extensions.Hosting.IHostBuilder hostBuilder, System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection> configureDelegate) { throw null; }
index 7859c87..874abe2 100644 (file)
@@ -2,11 +2,8 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.IO;
-using System.Reflection;
-using System.Runtime.InteropServices;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.EventLog;
 
 namespace Microsoft.Extensions.Hosting
 {
@@ -55,82 +52,8 @@ namespace Microsoft.Extensions.Hosting
         /// <returns>The initialized <see cref="IHostBuilder"/>.</returns>
         public static IHostBuilder CreateDefaultBuilder(string[] args)
         {
-            var builder = new HostBuilder();
-
-            builder.UseContentRoot(Directory.GetCurrentDirectory());
-            builder.ConfigureHostConfiguration(config =>
-            {
-                config.AddEnvironmentVariables(prefix: "DOTNET_");
-                if (args != null)
-                {
-                    config.AddCommandLine(args);
-                }
-            });
-
-            builder.ConfigureAppConfiguration((hostingContext, config) =>
-            {
-                IHostEnvironment env = hostingContext.HostingEnvironment;
-
-                bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
-
-                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
-                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
-
-                if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName))
-                {
-                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
-                    if (appAssembly != null)
-                    {
-                        config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
-                    }
-                }
-
-                config.AddEnvironmentVariables();
-
-                if (args != null)
-                {
-                    config.AddCommandLine(args);
-                }
-            })
-            .ConfigureLogging((hostingContext, logging) =>
-            {
-                bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
-                // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
-                // the defaults be overridden by the configuration.
-                if (isWindows)
-                {
-                    // Default the EventLogLoggerProvider to warning or above
-                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
-                }
-
-                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
-                logging.AddConsole();
-                logging.AddDebug();
-                logging.AddEventSourceLogger();
-
-                if (isWindows)
-                {
-                    // Add the EventLogLoggerProvider on windows machines
-                    logging.AddEventLog();
-                }
-
-                logging.Configure(options =>
-                {
-                    options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
-                                                        | ActivityTrackingOptions.TraceId
-                                                        | ActivityTrackingOptions.ParentId;
-                });
-
-            })
-            .UseDefaultServiceProvider((context, options) =>
-            {
-                bool isDevelopment = context.HostingEnvironment.IsDevelopment();
-                options.ValidateScopes = isDevelopment;
-                options.ValidateOnBuild = isDevelopment;
-            });
-
-            return builder;
+            HostBuilder builder = new();
+            return builder.ConfigureDefaults(args);
         }
     }
 }
index ca7e84a..28e32a4 100644 (file)
@@ -3,12 +3,16 @@
 
 using System;
 using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
 using System.Threading;
 using System.Threading.Tasks;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Hosting.Internal;
 using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.EventLog;
 
 namespace Microsoft.Extensions.Hosting
 {
@@ -45,7 +49,7 @@ namespace Microsoft.Extensions.Hosting
                 configBuilder.AddInMemoryCollection(new[]
                 {
                     new KeyValuePair<string, string>(HostDefaults.ContentRootKey,
-                        contentRoot  ?? throw new ArgumentNullException(nameof(contentRoot)))
+                        contentRoot ?? throw new ArgumentNullException(nameof(contentRoot)))
                 });
             });
         }
@@ -135,6 +139,105 @@ namespace Microsoft.Extensions.Hosting
         }
 
         /// <summary>
+        /// Configures an existing <see cref="IHostBuilder"/> instance with pre-configured defaults.
+        /// </summary>
+        /// <remarks>
+        ///   The following defaults are applied to the <see cref="IHostBuilder"/>:
+        ///   <list type="bullet">
+        ///     <item><description>set the <see cref="IHostEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/></description></item>
+        ///     <item><description>load host <see cref="IConfiguration"/> from "DOTNET_" prefixed environment variables</description></item>
+        ///     <item><description>load host <see cref="IConfiguration"/> from supplied command line args</description></item>
+        ///     <item><description>load app <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostEnvironment.EnvironmentName"/>].json'</description></item>
+        ///     <item><description>load app <see cref="IConfiguration"/> from User Secrets when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development' using the entry assembly</description></item>
+        ///     <item><description>load app <see cref="IConfiguration"/> from environment variables</description></item>
+        ///     <item><description>load app <see cref="IConfiguration"/> from supplied command line args</description></item>
+        ///     <item><description>configure the <see cref="ILoggerFactory"/> to log to the console, debug, and event source output</description></item>
+        ///     <item><description>enables scope validation on the dependency injection container when <see cref="IHostEnvironment.EnvironmentName"/> is 'Development'</description></item>
+        ///   </list>
+        /// </remarks>
+        /// <param name="builder">The existing builder to configure.</param>
+        /// <param name="args">The command line args.</param>
+        /// <returns>The same instance of the <see cref="IHostBuilder"/> for chaining.</returns>
+        public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args)
+        {
+            builder.UseContentRoot(Directory.GetCurrentDirectory());
+            builder.ConfigureHostConfiguration(config =>
+            {
+                config.AddEnvironmentVariables(prefix: "DOTNET_");
+                if (args is { Length: > 0 })
+                {
+                    config.AddCommandLine(args);
+                }
+            });
+
+            builder.ConfigureAppConfiguration((hostingContext, config) =>
+            {
+                IHostEnvironment env = hostingContext.HostingEnvironment;
+
+                bool reloadOnChange = hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
+
+                config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
+                      .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
+
+                if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
+                {
+                    var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
+                    if (appAssembly is not null)
+                    {
+                        config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
+                    }
+                }
+
+                config.AddEnvironmentVariables();
+
+                if (args is { Length: > 0 })
+                {
+                    config.AddCommandLine(args);
+                }
+            })
+            .ConfigureLogging((hostingContext, logging) =>
+            {
+                bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+
+                // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
+                // the defaults be overridden by the configuration.
+                if (isWindows)
+                {
+                    // Default the EventLogLoggerProvider to warning or above
+                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
+                }
+
+                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
+                logging.AddConsole();
+                logging.AddDebug();
+                logging.AddEventSourceLogger();
+
+                if (isWindows)
+                {
+                    // Add the EventLogLoggerProvider on windows machines
+                    logging.AddEventLog();
+                }
+
+                logging.Configure(options =>
+                {
+                    options.ActivityTrackingOptions =
+                        ActivityTrackingOptions.SpanId |
+                        ActivityTrackingOptions.TraceId |
+                        ActivityTrackingOptions.ParentId;
+                });
+
+            })
+            .UseDefaultServiceProvider((context, options) =>
+            {
+                bool isDevelopment = context.HostingEnvironment.IsDevelopment();
+                options.ValidateScopes = isDevelopment;
+                options.ValidateOnBuild = isDevelopment;
+            });
+
+            return builder;
+        }
+
+        /// <summary>
         /// Listens for Ctrl+C or SIGTERM and calls <see cref="IHostApplicationLifetime.StopApplication"/> to start the shutdown process.
         /// This will unblock extensions like RunAsync and WaitForShutdownAsync.
         /// </summary>
index d1e6125..df2d42a 100644 (file)
@@ -559,10 +559,23 @@ namespace Microsoft.Extensions.Hosting.Tests
             Assert.Same(appServicesFromHostBuilder, host.Services);
         }
 
+        [Fact]
+        public void HostBuilderConfigureDefaultsInterleavesMissingConfigValues()
+        {
+            IHostBuilder hostBuilder = new HostBuilder();
+            hostBuilder.ConfigureDefaults(args: null);
+
+            using var host = hostBuilder.Build();
+            var env = host.Services.GetRequiredService<IHostEnvironment>();
+
+            var expectedContentRootPath = Directory.GetCurrentDirectory();
+            Assert.Equal(expectedContentRootPath, env.ContentRootPath);
+        }
+
         private class FakeFileProvider : IFileProvider, IDisposable
         {
-            public bool Disposed { get; set; }
-            public void Dispose() { Disposed = true; }
+            public bool Disposed { get; private set; }
+            public void Dispose() => Disposed = true;
             public IDirectoryContents GetDirectoryContents(string subpath) => throw new NotImplementedException();
             public IFileInfo GetFileInfo(string subpath) => throw new NotImplementedException();
             public IChangeToken Watch(string filter) => throw new NotImplementedException();