From a78b9c7490846e60d37e14609921a1ebdc201fdc Mon Sep 17 00:00:00 2001 From: David Fowler Date: Thu, 9 Nov 2017 21:24:30 -0800 Subject: [PATCH] Support the Extensions.Abstractions from Hosting.Abstractions (#1259) - The goal here is to enable components that use hosting abstractions to use the web host. It lets us start to decouple components from the web host abstractions where possible while not breaking any existing components. This will allow things to work in both the generic host and the web host. The one snafu is the WebHostBuilderContext which has an IHostingEnvironment typed as the AspNetCore.Abstractions type. - Updated tests. #1218 --- .../Internal/ApplicationLifetime.cs | 2 +- .../Internal/HostingEnvironment.cs | 2 +- .../Internal/WebHost.cs | 5 ++++ .../Microsoft.AspNetCore.Hosting.csproj | 1 + .../WebHostBuilder.cs | 5 ++-- .../WebHostBuilderTests.cs | 14 ++++++++- .../WebHostTests.cs | 29 ++++++++++++++++++- 7 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/ApplicationLifetime.cs b/src/Microsoft.AspNetCore.Hosting/Internal/ApplicationLifetime.cs index 47d16e9654..958f8b5dcc 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/ApplicationLifetime.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/ApplicationLifetime.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal /// /// Allows consumers to perform cleanup during a graceful shutdown. /// - public class ApplicationLifetime : IApplicationLifetime + public class ApplicationLifetime : IApplicationLifetime, Extensions.Hosting.IApplicationLifetime { private readonly CancellationTokenSource _startedSource = new CancellationTokenSource(); private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource(); diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/HostingEnvironment.cs b/src/Microsoft.AspNetCore.Hosting/Internal/HostingEnvironment.cs index 34924ab365..1f8d1887d7 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/HostingEnvironment.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/HostingEnvironment.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.FileProviders; namespace Microsoft.AspNetCore.Hosting.Internal { - public class HostingEnvironment : IHostingEnvironment + public class HostingEnvironment : IHostingEnvironment, Extensions.Hosting.IHostingEnvironment { public string EnvironmentName { get; set; } = Hosting.EnvironmentName.Production; diff --git a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs index da74e1d1ea..203cea8137 100644 --- a/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs +++ b/src/Microsoft.AspNetCore.Hosting/Internal/WebHost.cs @@ -76,6 +76,11 @@ namespace Microsoft.AspNetCore.Hosting.Internal _applicationServiceCollection = appServices; _hostingServiceProvider = hostingServiceProvider; _applicationServiceCollection.AddSingleton(); + // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360 + _applicationServiceCollection.AddSingleton(sp => + { + return sp.GetRequiredService() as Extensions.Hosting.IApplicationLifetime; + }); _applicationServiceCollection.AddSingleton(); } diff --git a/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj b/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj index 67f9152238..830ff401e9 100644 --- a/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Microsoft.AspNetCore.Hosting/Microsoft.AspNetCore.Hosting.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs index f0dede89fd..31f07c6d71 100644 --- a/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs +++ b/src/Microsoft.AspNetCore.Hosting/WebHostBuilder.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Hosting /// public class WebHostBuilder : IWebHostBuilder { - private readonly IHostingEnvironment _hostingEnvironment; + private readonly HostingEnvironment _hostingEnvironment; private readonly List> _configureServicesDelegates; private IConfiguration _config; @@ -239,7 +239,8 @@ namespace Microsoft.AspNetCore.Hosting var services = new ServiceCollection(); services.AddSingleton(_options); - services.AddSingleton(_hostingEnvironment); + services.AddSingleton(_hostingEnvironment); + services.AddSingleton(_hostingEnvironment); services.AddSingleton(_context); var builder = new ConfigurationBuilder() diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs index f479c93a3d..a7c358141f 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostBuilderTests.cs @@ -526,6 +526,7 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { Assert.Equal(expected, host.Services.GetService().EnvironmentName); + Assert.Equal(expected, host.Services.GetService().EnvironmentName); } } @@ -568,6 +569,7 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { Assert.Equal("/", host.Services.GetService().ContentRootPath); + Assert.Equal("/", host.Services.GetService().ContentRootPath); } } @@ -581,8 +583,13 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var basePath = host.Services.GetRequiredService().ContentRootPath; + var basePath2 = host.Services.GetService().ContentRootPath; + Assert.True(Path.IsPathRooted(basePath)); Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath); + + Assert.True(Path.IsPathRooted(basePath2)); + Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath2); } } @@ -596,6 +603,7 @@ namespace Microsoft.AspNetCore.Hosting { var appBase = AppContext.BaseDirectory; Assert.Equal(appBase, host.Services.GetService().ContentRootPath); + Assert.Equal(appBase, host.Services.GetService().ContentRootPath); } } @@ -620,7 +628,9 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var hostingEnv = host.Services.GetService(); + var hostingEnv2 = host.Services.GetService(); Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv.ApplicationName); + Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv2.ApplicationName); } } @@ -634,7 +644,9 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var hostingEnv = host.Services.GetService(); + var hostingEnv2 = host.Services.GetService(); Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv.ApplicationName); + Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv2.ApplicationName); } } @@ -1046,7 +1058,7 @@ namespace Microsoft.AspNetCore.Hosting .ConfigureServices(services => services.AddSingleton(loggerProvider.Sink)) .ConfigureLogging((_, lf) => lf.AddProvider(loggerProvider)) .ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.AddInMemoryCollection( - new [] + new[] { new KeyValuePair("testhostingstartup:config", "value") })); diff --git a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs index 001f267889..70d12561d3 100644 --- a/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs +++ b/test/Microsoft.AspNetCore.Hosting.Tests/WebHostTests.cs @@ -167,6 +167,7 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var lifetime = host.Services.GetRequiredService(); + var lifetime2 = host.Services.GetRequiredService(); var server = (FakeServer)host.Services.GetRequiredService(); var cts = new CancellationTokenSource(); @@ -175,6 +176,7 @@ namespace Microsoft.AspNetCore.Hosting // Wait on the host to be started lifetime.ApplicationStarted.WaitHandle.WaitOne(); + Assert.True(lifetime2.ApplicationStarted.IsCancellationRequested); Assert.Equal(1, server.StartInstances.Count); Assert.Equal(0, server.StartInstances[0].DisposeCalls); @@ -183,6 +185,7 @@ namespace Microsoft.AspNetCore.Hosting // Wait on the host to shutdown lifetime.ApplicationStopped.WaitHandle.WaitOne(); + Assert.True(lifetime2.ApplicationStopped.IsCancellationRequested); // Wait for RunAsync to finish to guarantee Disposal of WebHost await runInBackground; @@ -408,11 +411,14 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var applicationLifetime = host.Services.GetService(); + var applicationLifetime2 = host.Services.GetService(); Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested); + Assert.False(applicationLifetime2.ApplicationStarted.IsCancellationRequested); await host.StartAsync(); Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested); + Assert.True(applicationLifetime2.ApplicationStarted.IsCancellationRequested); } } @@ -424,17 +430,26 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var applicationLifetime = host.Services.GetService(); + var applicationLifetime2 = host.Services.GetService(); var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted); var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping); var stopped = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopped); + var started2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStarted); + var stopping2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopping); + var stopped2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopped); + await host.StartAsync(); Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested); + Assert.True(applicationLifetime2.ApplicationStarted.IsCancellationRequested); Assert.True(started.All(s => s)); + Assert.True(started2.All(s => s)); host.Dispose(); Assert.True(stopping.All(s => s)); + Assert.True(stopping2.All(s => s)); Assert.True(stopped.All(s => s)); + Assert.True(stopped2.All(s => s)); } } @@ -663,18 +678,24 @@ namespace Microsoft.AspNetCore.Hosting .Build()) { var applicationLifetime = host.Services.GetService(); + var applicationLifetime2 = host.Services.GetService(); var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted); var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping); + var started2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStarted); + var stopping2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopping); + await host.StartAsync(); Assert.True(events1[0]); Assert.True(events2[0]); Assert.True(started.All(s => s)); + Assert.True(started2.All(s => s)); host.Dispose(); Assert.True(events1[1]); Assert.True(events2[1]); Assert.True(stopping.All(s => s)); + Assert.True(stopping2.All(s => s)); } } @@ -689,7 +710,9 @@ namespace Microsoft.AspNetCore.Hosting { await host.StartAsync(); var env = host.Services.GetService(); + var env2 = host.Services.GetService(); Assert.Equal("Changed", env.EnvironmentName); + Assert.Equal("Changed", env2.EnvironmentName); } } @@ -771,7 +794,9 @@ namespace Microsoft.AspNetCore.Hosting using (var host = CreateBuilder().UseFakeServer().Build()) { var env = host.Services.GetService(); + var env2 = host.Services.GetService(); Assert.Equal(EnvironmentName.Production, env.EnvironmentName); + Assert.Equal(EnvironmentName.Production, env2.EnvironmentName); } } @@ -790,6 +815,8 @@ namespace Microsoft.AspNetCore.Hosting using (var host = CreateBuilder(config).UseFakeServer().Build()) { var env = host.Services.GetService(); + var env2 = host.Services.GetService(); + Assert.Equal(EnvironmentName.Staging, env.EnvironmentName); Assert.Equal(EnvironmentName.Staging, env.EnvironmentName); } } @@ -990,7 +1017,7 @@ namespace Microsoft.AspNetCore.Hosting { private readonly IApplicationLifetime _lifetime; - public TestHostedService(IApplicationLifetime lifetime) + public TestHostedService(IApplicationLifetime lifetime, Extensions.Hosting.IApplicationLifetime lifetime2) { _lifetime = lifetime; }