Allow configuration and logging to be configured on WebHostBuilder, setting up for the removal of AddProvider from ILoggerFactory.

This commit is contained in:
glennc 2017-03-21 19:42:49 -07:00 committed by John Luo
parent 4b8de0b0f1
commit 0ab882b6d3
9 changed files with 376 additions and 12 deletions

View File

@ -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
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging);
/// <summary>
/// Adds a delegate for configuring the provided <see cref="ILoggerFactory"/>. This may be called multiple times.
/// </summary>
/// <param name="configureLogging">The delegate that configures the <see cref="ILoggerFactory"/>.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder ConfigureLogging<T>(Action<T> configureLogging) where T : ILoggerFactory;
/// <summary>
/// Add or replace a setting in the configuration.
/// </summary>
@ -52,5 +60,21 @@ namespace Microsoft.AspNetCore.Hosting
/// <param name="key">The key of the setting to look up.</param>
/// <returns>The value the setting currently contains.</returns>
string GetSetting(string key);
/// <summary>
/// Adds a delegate to construct the <see cref="ILoggerFactory"/> that will be registered
/// as a singleton and used by the application.
/// </summary>
/// <param name="createLoggerFactory">The delegate that constructs an <see cref="IConfigurationBuilder" /></param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder UseLoggerFactory(Func<WebHostBuilderContext, ILoggerFactory> createLoggerFactory);
/// <summary>
/// Adds a delegate for configuring the <see cref="IConfigurationBuilder"/> that will construct an <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder" /> that will be used to construct an <see cref="IConfiguration" />.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
IWebHostBuilder ConfigureConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate);
}
}

View File

@ -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
{
/// <summary>
/// Context containing the common services on the <see cref="IWebHost" />. Some properties may be null until set by the <see cref="IWebHost" />.
/// </summary>
public class WebHostBuilderContext
{
/// <summary>
/// The <see cref="IHostingEnvironment" /> initialized by the <see cref="IWebHost" />.
/// </summary>
public IHostingEnvironment HostingEnvironment { get; set; }
/// <summary>
/// The <see cref="IConfiguration" /> containing the merged configuration of the application and the <see cref="IWebHost" />.
/// </summary>
public IConfiguration Configuration { get; set; }
/// <summary>
/// The <see cref="ILoggerFactory" /> configured on the <see cref="IWebHost" />.
/// </summary>
public ILoggerFactory LoggerFactory { get; set; }
}
}

View File

@ -0,0 +1,20 @@
[
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureLogging<T0>(System.Action<T0> 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<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Logging.ILoggerFactory> 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<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate)",
"Kind": "Addition"
}
]

View File

@ -0,0 +1,20 @@
[
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHostBuilder",
"NewMemberId": "Microsoft.AspNetCore.Hosting.IWebHostBuilder ConfigureLogging<T0>(System.Action<T0> 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<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Logging.ILoggerFactory> 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<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder> configureDelegate)",
"Kind": "Addition"
}
]

View File

@ -21,6 +21,7 @@
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(AspNetCoreVersion)" />

View File

@ -29,9 +29,10 @@ namespace Microsoft.AspNetCore.Hosting
private readonly List<Action<ILoggerFactory>> _configureLoggingDelegates;
private IConfiguration _config;
private ILoggerFactory _loggerFactory;
private WebHostOptions _options;
private bool _webHostBuilt;
private Func<WebHostBuilderContext, ILoggerFactory> _createLoggerFactoryDelegate;
private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureConfigurationBuilderDelegates;
/// <summary>
/// Initializes a new instance of the <see cref="WebHostBuilder"/> class.
@ -41,6 +42,7 @@ namespace Microsoft.AspNetCore.Hosting
_hostingEnvironment = new HostingEnvironment();
_configureServicesDelegates = new List<Action<IServiceCollection>>();
_configureLoggingDelegates = new List<Action<ILoggerFactory>>();
_configureConfigurationBuilderDelegates = new List<Action<WebHostBuilderContext, IConfigurationBuilder>>();
_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;
}
/// <summary>
/// Adds a delegate to construct the <see cref="ILoggerFactory"/> that will be registered
/// as a singleton and used by the application.
/// </summary>
/// <param name="createLoggerFactory">The delegate that constructs an <see cref="IConfigurationBuilder" /></param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder UseLoggerFactory(Func<WebHostBuilderContext, ILoggerFactory> createLoggerFactory)
{
if (createLoggerFactory == null)
{
throw new ArgumentNullException(nameof(createLoggerFactory));
}
_createLoggerFactoryDelegate = createLoggerFactory;
return this;
}
/// <summary>
/// Adds a delegate for configuring the provided <see cref="ILoggerFactory"/>. This may be called multiple times.
/// </summary>
/// <param name="configureLogging">The delegate that configures the <see cref="ILoggerFactory"/>.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder ConfigureLogging<T>(Action<T> configureLogging) where T : ILoggerFactory
{
if (configureLogging == null)
{
throw new ArgumentNullException(nameof(configureLogging));
}
_configureLoggingDelegates.Add(factory =>
{
if (factory is T typedFactory)
{
configureLogging(typedFactory);
}
});
return this;
}
/// <summary>
/// Adds a delegate for configuring the <see cref="IConfigurationBuilder"/> that will construct an <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder" /> that will be used to construct an <see cref="IConfiguration" />.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public IWebHostBuilder ConfigureConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
{
if (configureDelegate == null)
{
throw new ArgumentNullException(nameof(configureDelegate));
}
_configureConfigurationBuilderDelegates.Add(configureDelegate);
return this;
}
/// <summary>
/// Builds the required services and an <see cref="IWebHost"/> which hosts a web application.
/// </summary>
@ -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<IConfiguration>(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<Exception>();
@ -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.

View File

@ -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<IServiceProviderFactory<IServiceCollection>>(new DefaultServiceProviderFactory(options)));
});
}
/// <summary>
/// Configures and use a <see cref="LoggerFactory"/> for the web host.
/// </summary>
/// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
/// <param name="configure">A callback used to configure the <see cref="LoggerFactory"/> that will be added as a singleton and used by the application.</param>
/// <returns>The <see cref="IWebHostBuilder"/>.</returns>
public static IWebHostBuilder UseLoggerFactory(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, LoggerFactory> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
hostBuilder.UseLoggerFactory(context =>
{
var loggerFactory = new LoggerFactory();
configure(context, loggerFactory);
return loggerFactory;
});
return hostBuilder;
}
}
}

View File

@ -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<CustomLoggerFactory> 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() { }
}
}

View File

@ -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<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Same(loggerFactory, host.Services.GetService<ILoggerFactory>());
}
[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<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal(2, callCount);
Assert.Same(loggerFactory, host.Services.GetService<ILoggerFactory>());
}
[Fact]
public void ConfigureLoggingCalledIfLoggerFactoryTypeMatches()
{
var callCount = 0;
var hostBuilder = new WebHostBuilder()
.UseLoggerFactory(_ => new SubLoggerFactory())
.ConfigureLogging<CustomLoggerFactory>(factory =>
{
Assert.Equal(0, callCount++);
})
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.Equal(1, callCount);
}
[Fact]
public void ConfigureLoggingNotCalledIfLoggerFactoryTypeDoesNotMatches()
{
var callCount = 0;
var hostBuilder = new WebHostBuilder()
.UseLoggerFactory(_ => new NonSubLoggerFactory())
.ConfigureLogging<CustomLoggerFactory>(factory =>
{
Assert.Equal(0, callCount++);
})
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
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<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.IsType(typeof(CustomLoggerFactory), host.Services.GetService<ILoggerFactory>());
}
[Fact]
public void ThereIsAlwaysConfiguration()
{
var hostBuilder = new WebHostBuilder()
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
Assert.NotNull(host.Services.GetService<IConfiguration>());
}
[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<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
}
[Fact]
public void CanConfigureConfigurationAndRetrieveFromDI()
{
var hostBuilder = new WebHostBuilder()
.ConfigureConfiguration((_, configBuilder) =>
{
configBuilder
.AddInMemoryCollection(
new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("key1", "value1")
})
.AddEnvironmentVariables();
})
.UseServer(new TestServer())
.UseStartup<StartupNoServices>();
var host = (WebHost)hostBuilder.Build();
var config = host.Services.GetService<IConfiguration>();
Assert.NotNull(config);
Assert.Equal("value1", config["key1"]);
}
[Fact]
public void DoNotCaptureStartupErrorsByDefault()
{