diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs index 17274117eb..b37fb88b55 100644 --- a/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/IWebHostBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -38,6 +39,13 @@ namespace Microsoft.AspNetCore.Hosting /// The . IWebHostBuilder ConfigureLogging(Action configureLogging); + /// + /// Adds a delegate for configuring the provided . This may be called multiple times. + /// + /// The delegate that configures the . + /// The . + IWebHostBuilder ConfigureLogging(Action configureLogging) where T : ILoggerFactory; + /// /// Add or replace a setting in the configuration. /// @@ -52,5 +60,21 @@ namespace Microsoft.AspNetCore.Hosting /// The key of the setting to look up. /// The value the setting currently contains. string GetSetting(string key); + + /// + /// Adds a delegate to construct the that will be registered + /// as a singleton and used by the application. + /// + /// The delegate that constructs an + /// The . + IWebHostBuilder UseLoggerFactory(Func createLoggerFactory); + + + /// + /// Adds a delegate for configuring the that will construct an . + /// + /// The delegate for configuring the that will be used to construct an . + /// The . + IWebHostBuilder ConfigureConfiguration(Action configureDelegate); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostBuilderContext.cs b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostBuilderContext.cs new file mode 100644 index 0000000000..5a973abd0a --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/WebHostBuilderContext.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Hosting +{ + /// + /// Context containing the common services on the . Some properties may be null until set by the . + /// + public class WebHostBuilderContext + { + /// + /// The initialized by the . + /// + public IHostingEnvironment HostingEnvironment { get; set; } + + /// + /// The containing the merged configuration of the application and the . + /// + public IConfiguration Configuration { get; set; } + + /// + /// The configured on the . + /// + public ILoggerFactory LoggerFactory { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.net45.json b/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.net45.json new file mode 100644 index 0000000000..991748d863 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.net45.json @@ -0,0 +1,20 @@ +[ + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureLogging(System.Action configureLogging) where T0 : Microsoft.Extensions.Logging.ILoggerFactory", + "Kind": "Addition" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder UseLoggerFactory(System.Func createLoggerFactory)", + "Kind": "Addition" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureConfiguration(System.Action configureDelegate)", + "Kind": "Addition" + } +] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.netcore.json b/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.netcore.json new file mode 100644 index 0000000000..991748d863 --- /dev/null +++ b/src/Microsoft.AspNetCore.Hosting.Abstractions/exceptions.netcore.json @@ -0,0 +1,20 @@ +[ + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureLogging(System.Action configureLogging) where T0 : Microsoft.Extensions.Logging.ILoggerFactory", + "Kind": "Addition" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder UseLoggerFactory(System.Func createLoggerFactory)", + "Kind": "Addition" + }, + { + "OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureConfiguration(System.Action configureDelegate)", + "Kind": "Addition" + } +] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj b/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj index 344c4cc654..1f5c69fcb4 100644 --- a/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs index 0b4b2d154c..48a357aed6 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs @@ -29,9 +29,10 @@ namespace Microsoft.AspNetCore.Hosting private readonly List> _configureLoggingDelegates; private IConfiguration _config; - private ILoggerFactory _loggerFactory; private WebHostOptions _options; private bool _webHostBuilt; + private Func _createLoggerFactoryDelegate; + private List> _configureConfigurationBuilderDelegates; /// /// Initializes a new instance of the class. @@ -41,6 +42,7 @@ namespace Microsoft.AspNetCore.Hosting _hostingEnvironment = new HostingEnvironment(); _configureServicesDelegates = new List>(); _configureLoggingDelegates = new List>(); + _configureConfigurationBuilderDelegates = new List>(); _config = new ConfigurationBuilder() .AddEnvironmentVariables(prefix: "ASPNETCORE_") @@ -94,7 +96,7 @@ namespace Microsoft.AspNetCore.Hosting throw new ArgumentNullException(nameof(loggerFactory)); } - _loggerFactory = loggerFactory; + _createLoggerFactoryDelegate = _ => loggerFactory; return this; } @@ -131,6 +133,60 @@ namespace Microsoft.AspNetCore.Hosting return this; } + /// + /// Adds a delegate to construct the that will be registered + /// as a singleton and used by the application. + /// + /// The delegate that constructs an + /// The . + public IWebHostBuilder UseLoggerFactory(Func createLoggerFactory) + { + if (createLoggerFactory == null) + { + throw new ArgumentNullException(nameof(createLoggerFactory)); + } + + _createLoggerFactoryDelegate = createLoggerFactory; + return this; + } + + /// + /// Adds a delegate for configuring the provided . This may be called multiple times. + /// + /// The delegate that configures the . + /// The . + public IWebHostBuilder ConfigureLogging(Action configureLogging) where T : ILoggerFactory + { + if (configureLogging == null) + { + throw new ArgumentNullException(nameof(configureLogging)); + } + _configureLoggingDelegates.Add(factory => + { + if (factory is T typedFactory) + { + configureLogging(typedFactory); + } + }); + return this; + } + + /// + /// Adds a delegate for configuring the that will construct an . + /// + /// The delegate for configuring the that will be used to construct an . + /// The . + public IWebHostBuilder ConfigureConfiguration(Action configureDelegate) + { + if (configureDelegate == null) + { + throw new ArgumentNullException(nameof(configureDelegate)); + } + + _configureConfigurationBuilderDelegates.Add(configureDelegate); + return this; + } + /// /// Builds the required services and an which hosts a web application. /// @@ -185,23 +241,35 @@ namespace Microsoft.AspNetCore.Hosting var appEnvironment = PlatformServices.Default.Application; var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, appEnvironment.ApplicationBasePath); var applicationName = _options.ApplicationName ?? appEnvironment.ApplicationName; + var hostingContext = new WebHostBuilderContext + { + Configuration = _config + }; // Initialize the hosting environment _hostingEnvironment.Initialize(applicationName, contentRootPath, _options); + hostingContext.HostingEnvironment = _hostingEnvironment; var services = new ServiceCollection(); services.AddSingleton(_hostingEnvironment); + var builder = new ConfigurationBuilder() + .SetBasePath(_hostingEnvironment.ContentRootPath) + .AddInMemoryCollection(_config.AsEnumerable()); + + foreach (var configureConfiguration in _configureConfigurationBuilderDelegates) + { + configureConfiguration(hostingContext, builder); + } + + var configuration = builder.Build(); + services.AddSingleton(configuration); + hostingContext.Configuration = configuration; + // The configured ILoggerFactory is added as a singleton here. AddLogging below will not add an additional one. - if (_loggerFactory == null) - { - _loggerFactory = new LoggerFactory(); - services.AddSingleton(provider => _loggerFactory); - } - else - { - services.AddSingleton(_loggerFactory); - } + var loggerFactory = _createLoggerFactoryDelegate?.Invoke(hostingContext) ?? new LoggerFactory(); + services.AddSingleton(loggerFactory); + hostingContext.LoggerFactory = loggerFactory; var exceptions = new List(); @@ -236,9 +304,10 @@ namespace Microsoft.AspNetCore.Hosting } } + // Kept for back-compat, will remove once ConfigureLogging is removed. foreach (var configureLogging in _configureLoggingDelegates) { - configureLogging(_loggerFactory); + configureLogging(loggerFactory); } //This is required to add ILogger of T. diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs index 6e2b7a7552..50a451a331 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilderExtensions.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Hosting { @@ -92,5 +93,28 @@ namespace Microsoft.AspNetCore.Hosting services.Replace(ServiceDescriptor.Singleton>(new DefaultServiceProviderFactory(options))); }); } + + /// + /// Configures and use a for the web host. + /// + /// The to configure. + /// A callback used to configure the that will be added as a singleton and used by the application. + /// The . + public static IWebHostBuilder UseLoggerFactory(this IWebHostBuilder hostBuilder, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + hostBuilder.UseLoggerFactory(context => + { + var loggerFactory = new LoggerFactory(); + configure(context, loggerFactory); + return loggerFactory; + }); + + return hostBuilder; + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/CustomLoggerFactory.cs b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/CustomLoggerFactory.cs new file mode 100644 index 0000000000..c09d42a579 --- /dev/null +++ b/test/Microsoft.AspNetCore.Hosting.Tests/Fakes/CustomLoggerFactory.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Hosting.Fakes +{ + public class CustomLoggerFactory : ILoggerFactory + { + public void CustomConfigureMethod() { } + + public void AddProvider(ILoggerProvider provider) { } + + public ILogger CreateLogger(string categoryName) => NullLogger.Instance; + + public void Dispose() { } + } + + public static class CustomLoggerFactoryExtensions + { + public static IWebHostBuilder ConfigureCustomLogger(this IWebHostBuilder builder, Action configureLogger) + { + builder.UseLoggerFactory(_ => new CustomLoggerFactory()); + builder.ConfigureLogging(configureLogger); + return builder; + } + } + + public class SubLoggerFactory : CustomLoggerFactory { } + + public class NonSubLoggerFactory : ILoggerFactory + { + public void CustomConfigureMethod() { } + + public void AddProvider(ILoggerProvider provider) { } + + public ILogger CreateLogger(string categoryName) => NullLogger.Instance; + + public void Dispose() { } + } +} diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index ac5a543479..f8e8a4d89b 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -219,6 +219,140 @@ namespace Microsoft.AspNetCore.Hosting Assert.Equal(2, callCount); } + [Fact] + public void UseLoggerFactoryDelegateIsHonored() + { + var loggerFactory = new LoggerFactory(); + + var hostBuilder = new WebHostBuilder() + .UseLoggerFactory(_ => loggerFactory) + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + + Assert.Same(loggerFactory, host.Services.GetService()); + } + + [Fact] + public void UseLoggerFactoryFuncAndConfigureLoggingCompose() + { + var callCount = 0; //Verify that multiple configureLogging calls still compose correctly. + var loggerFactory = new LoggerFactory(); + var hostBuilder = new WebHostBuilder() + .UseLoggerFactory(_ => loggerFactory) + .ConfigureLogging(factory => + { + Assert.Equal(0, callCount++); + }) + .ConfigureLogging(factory => + { + Assert.Equal(1, callCount++); + }) + .UseServer(new TestServer()) + .UseStartup(); + var host = (WebHost)hostBuilder.Build(); + Assert.Equal(2, callCount); + Assert.Same(loggerFactory, host.Services.GetService()); + } + + [Fact] + public void ConfigureLoggingCalledIfLoggerFactoryTypeMatches() + { + var callCount = 0; + var hostBuilder = new WebHostBuilder() + .UseLoggerFactory(_ => new SubLoggerFactory()) + .ConfigureLogging(factory => + { + Assert.Equal(0, callCount++); + }) + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + Assert.Equal(1, callCount); + } + + [Fact] + public void ConfigureLoggingNotCalledIfLoggerFactoryTypeDoesNotMatches() + { + var callCount = 0; + var hostBuilder = new WebHostBuilder() + .UseLoggerFactory(_ => new NonSubLoggerFactory()) + .ConfigureLogging(factory => + { + Assert.Equal(0, callCount++); + }) + .UseServer(new TestServer()) + .UseStartup(); + + var host = (WebHost)hostBuilder.Build(); + Assert.Equal(0, callCount); + } + + [Fact] + public void CanUseCustomLoggerFactory() + { + var hostBuilder = new WebHostBuilder() + .ConfigureCustomLogger(factory => + { + factory.CustomConfigureMethod(); + }) + .UseServer(new TestServer()) + .UseStartup(); + var host = (WebHost)hostBuilder.Build(); + Assert.IsType(typeof(CustomLoggerFactory), host.Services.GetService()); + } + + [Fact] + public void ThereIsAlwaysConfiguration() + { + var hostBuilder = new WebHostBuilder() + .UseServer(new TestServer()) + .UseStartup(); + var host = (WebHost)hostBuilder.Build(); + + Assert.NotNull(host.Services.GetService()); + } + + [Fact] + public void ConfigureConfigurationSettingsPropagated() + { + var hostBuilder = new WebHostBuilder() + .UseSetting("key1", "value1") + .ConfigureConfiguration((context, configBuilder) => + { + var config = configBuilder.Build(); + Assert.Equal("value1", config["key1"]); + }) + .UseServer(new TestServer()) + .UseStartup(); + var host = (WebHost)hostBuilder.Build(); + } + + [Fact] + public void CanConfigureConfigurationAndRetrieveFromDI() + { + var hostBuilder = new WebHostBuilder() + .ConfigureConfiguration((_, configBuilder) => + { + configBuilder + .AddInMemoryCollection( + new KeyValuePair[] + { + new KeyValuePair("key1", "value1") + }) + .AddEnvironmentVariables(); + }) + .UseServer(new TestServer()) + .UseStartup(); + var host = (WebHost)hostBuilder.Build(); + + var config = host.Services.GetService(); + Assert.NotNull(config); + Assert.Equal("value1", config["key1"]); + } + [Fact] public void DoNotCaptureStartupErrorsByDefault() {