#947 Add IServer.StopAsyc, IWebHost.StopAsync, and make Start async

This commit is contained in:
Chris R 2017-03-27 10:30:45 -07:00
parent 5f9fa5c009
commit 62f74d5be0
18 changed files with 548 additions and 176 deletions

View File

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
@ -34,8 +36,9 @@ namespace SampleStartups
.Start(_urls.ToArray());
}
public void Stop()
public async Task StopAsync()
{
await _host.StopAsync(TimeSpan.FromSeconds(5));
_host.Dispose();
}

View File

@ -3,6 +3,8 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -166,7 +168,7 @@ namespace Microsoft.AspNetCore.Hosting
public static IWebHost Start(this IWebHostBuilder hostBuilder, params string[] urls)
{
var host = hostBuilder.UseUrls(urls).Build();
host.Start();
host.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
return host;
}
}

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Hosting
@ -24,6 +26,13 @@ namespace Microsoft.AspNetCore.Hosting
/// <summary>
/// Starts listening on the configured addresses.
/// </summary>
void Start();
Task StartAsync(CancellationToken cancellationToken);
/// <summary>
/// Attempt to gracefully stop the host.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task StopAsync(CancellationToken cancellationToken);
}
}

View File

@ -16,5 +16,7 @@ namespace Microsoft.AspNetCore.Hosting
public static readonly string ServerUrlsKey = "urls";
public static readonly string ContentRootKey = "contentRoot";
public static readonly string PreferHostingUrls = "preferHostingUrls";
public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
}
}

View File

@ -16,5 +16,24 @@
"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"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"OldMemberId": "System.Void Start()",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Modification"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
}
]

View File

@ -16,5 +16,24 @@
"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"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"OldMemberId": "System.Void Start()",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Modification"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.IWebHost : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
}
]

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Hosting.Server
@ -21,6 +23,13 @@ namespace Microsoft.AspNetCore.Hosting.Server
/// </summary>
/// <param name="application">An instance of <see cref="IHttpApplication{TContext}"/>.</param>
/// <typeparam name="TContext">The context associated with the application.</typeparam>
void Start<TContext>(IHttpApplication<TContext> application);
/// <param name="cancellationToken">Indicates if the server startup should be aborted.</param>
Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);
/// <summary>
/// Stop processing requests and shut down the server, gracefully if possible.
/// </summary>
/// <param name="cancellationToken">Indicates if the graceful shutdown should be aborted.</param>
Task StopAsync(CancellationToken cancellationToken);
}
}

View File

@ -10,5 +10,24 @@
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
"NewMemberId": "System.Void set_PreferHostingUrls(System.Boolean value)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"OldMemberId": "System.Void Start<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application)",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
"Kind": "Modification"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
}
]

View File

@ -10,5 +10,24 @@
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
"NewMemberId": "System.Void set_PreferHostingUrls(System.Boolean value)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"OldMemberId": "System.Void Start<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application)",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
"Kind": "Modification"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StartAsync<T0>(Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0> application, System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
},
{
"OldTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewTypeId": "public interface Microsoft.AspNetCore.Hosting.Server.IServer : System.IDisposable",
"NewMemberId": "System.Threading.Tasks.Task StopAsync(System.Threading.CancellationToken cancellationToken)",
"Kind": "Addition"
}
]

View File

@ -82,6 +82,16 @@ namespace Microsoft.AspNetCore.Hosting.Internal
}
}
public static void ServerShutdownException(this ILogger logger, Exception ex)
{
if (logger.IsEnabled(LogLevel.Debug))
{
logger.LogDebug(
eventId: LoggerEventIds.ServerShutdownException,
exception: ex,
message: "Server shutdown exception");
}
}
private class HostingLogScope : IReadOnlyList<KeyValuePair<string, object>>
{

View File

@ -16,5 +16,6 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public const int HostedServiceStartException = 9;
public const int HostedServiceStopException = 10;
public const int HostingStartupAssemblyException = 11;
public const int ServerShutdownException = 12;
}
}

View File

@ -7,6 +7,8 @@ using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Builder;
using Microsoft.AspNetCore.Hosting.Server;
@ -39,6 +41,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
private RequestDelegate _application;
private ILogger<WebHost> _logger;
private bool _stopped;
// Used for testing only
internal WebHostOptions Options => _options;
@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Hosting.Internal
}
}
public virtual void Start()
public virtual async Task StartAsync(CancellationToken cancellationToken)
{
HostingEventSource.Log.HostStart();
_logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
@ -112,7 +116,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
_hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
Server.Start(new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory));
var hostingApp = new HostingApplication(_application, _logger, diagnosticSource, httpContextFactory);
await Server.StartAsync(hostingApp, cancellationToken);
// Fire IApplicationLifetime.Started
_applicationLifetime?.NotifyStarted();
@ -267,23 +272,51 @@ namespace Microsoft.AspNetCore.Hosting.Internal
}
}
public void Dispose()
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_stopped)
{
return;
}
_stopped = true;
_logger?.Shutdown();
if (!cancellationToken.CanBeCanceled)
{
cancellationToken = new CancellationTokenSource(Options.ShutdownTimeout).Token;
}
// Fire IApplicationLifetime.Stopping
_applicationLifetime?.StopApplication();
await Server?.StopAsync(cancellationToken);
// Fire the IHostedService.Stop
_hostedServiceExecutor?.Stop();
(_hostingServiceProvider as IDisposable)?.Dispose();
(_applicationServices as IDisposable)?.Dispose();
// Fire IApplicationLifetime.Stopped
_applicationLifetime?.NotifyStopped();
HostingEventSource.Log.HostStop();
}
public void Dispose()
{
if (!_stopped)
{
try
{
this.StopAsync().GetAwaiter().GetResult();
}
catch (Exception ex)
{
_logger?.ServerShutdownException(ex);
}
}
(_applicationServices as IDisposable)?.Dispose();
(_hostingServiceProvider as IDisposable)?.Dispose();
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Extensions.Configuration;
namespace Microsoft.AspNetCore.Hosting.Internal
@ -27,6 +28,13 @@ namespace Microsoft.AspNetCore.Hosting.Internal
ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
HostingStartupAssemblies = configuration[WebHostDefaults.HostingStartupAssembliesKey]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];
PreferHostingUrls = ParseBool(configuration, WebHostDefaults.PreferHostingUrls);
var timeout = configuration[WebHostDefaults.ShutdownTimeoutKey];
if (!string.IsNullOrEmpty(timeout)
&& int.TryParse(timeout, NumberStyles.None, CultureInfo.InvariantCulture, out var seconds))
{
ShutdownTimeout = TimeSpan.FromSeconds(seconds);
}
}
public string ApplicationName { get; set; }
@ -47,6 +55,8 @@ namespace Microsoft.AspNetCore.Hosting.Internal
public bool PreferHostingUrls { get; set; }
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
private static bool ParseBool(IConfiguration configuration, string key)
{
return string.Equals("true", configuration[key], StringComparison.OrdinalIgnoreCase)

View File

@ -7,6 +7,7 @@ using System.Reflection;
using System.Runtime.Loader;
#endif
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
@ -14,11 +15,62 @@ namespace Microsoft.AspNetCore.Hosting
{
public static class WebHostExtensions
{
/// <summary>
/// Starts the host.
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public static void Start(this IWebHost host)
{
host.StartAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Starts the host.
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public static Task StartAsync(this IWebHost host)
{
return host.StartAsync(CancellationToken.None);
}
/// <summary>
/// Gracefully stops the host.
/// </summary>
/// <param name="host"></param>
/// <returns></returns>
public static Task StopAsync(this IWebHost host)
{
return host.StopAsync(CancellationToken.None);
}
/// <summary>
/// Attempts to gracefully stop the host with the given timeout.
/// </summary>
/// <param name="host"></param>
/// <param name="timeout">The timeout for stopping gracefully. Once expired the
/// server may terminate any remaining active connections.</param>
/// <returns></returns>
public static Task StopAsync(this IWebHost host, TimeSpan timeout)
{
return host.StopAsync(new CancellationTokenSource(timeout).Token);
}
/// <summary>
/// Runs a web application and block the calling thread until host shutdown.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
public static void Run(this IWebHost host)
{
host.RunAsync().GetAwaiter().GetResult();
}
/// <summary>
/// Runs a web application and returns a Task that only completes on host shutdown.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
public static async Task RunAsync(this IWebHost host)
{
var done = new ManualResetEventSlim(false);
using (var cts = new CancellationTokenSource())
@ -28,7 +80,13 @@ namespace Microsoft.AspNetCore.Hosting
if (!cts.IsCancellationRequested)
{
Console.WriteLine("Application is shutting down...");
cts.Cancel();
try
{
cts.Cancel();
}
catch (ObjectDisposedException)
{
}
}
done.Wait();
@ -48,26 +106,26 @@ namespace Microsoft.AspNetCore.Hosting
eventArgs.Cancel = true;
};
host.Run(cts.Token, "Application started. Press Ctrl+C to shut down.");
await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
done.Set();
}
}
/// <summary>
/// Runs a web application and block the calling thread until token is triggered or shutdown is triggered.
/// Runs a web application and and returns a Task that only completes when the token is triggered or shutdown is triggered.
/// </summary>
/// <param name="host">The <see cref="IWebHost"/> to run.</param>
/// <param name="token">The token to trigger shutdown.</param>
public static void Run(this IWebHost host, CancellationToken token)
public static Task RunAsync(this IWebHost host, CancellationToken token)
{
host.Run(token, shutdownMessage: null);
return host.RunAsync(token, shutdownMessage: null);
}
private static void Run(this IWebHost host, CancellationToken token, string shutdownMessage)
private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage)
{
using (host)
{
host.Start();
await host.StartAsync(token);
var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
@ -95,7 +153,17 @@ namespace Microsoft.AspNetCore.Hosting
},
applicationLifetime);
applicationLifetime.ApplicationStopping.WaitHandle.WaitOne();
var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
applicationLifetime.ApplicationStopping.Register(obj =>
{
var tcs = (TaskCompletionSource<object>)obj;
tcs.TrySetResult(null);
}, waitForStop);
await waitForStop.Task;
// WebHost will use its default ShutdownTimeout if none is specified.
await host.StopAsync();
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
@ -39,7 +40,7 @@ namespace Microsoft.AspNetCore.TestHost
Features = featureCollection;
var host = builder.UseServer(this).Build();
host.Start();
host.StartAsync().GetAwaiter().GetResult();
_hostInstance = host;
}
@ -91,7 +92,7 @@ namespace Microsoft.AspNetCore.TestHost
}
}
void IServer.Start<TContext>(IHttpApplication<TContext> application)
Task IServer.StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
_application = new ApplicationWrapper<Context>((IHttpApplication<Context>)application, () =>
{
@ -100,6 +101,13 @@ namespace Microsoft.AspNetCore.TestHost
throw new ObjectDisposedException(GetType().FullName);
}
});
return Task.CompletedTask;
}
Task IServer.StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
private class ApplicationWrapper<TContext> : IHttpApplication<TContext>

View File

@ -1,11 +1,12 @@
// 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.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Configuration;
namespace ServerComparison.TestSites
{
@ -36,8 +37,14 @@ namespace ServerComparison.TestSites
public IFeatureCollection Features { get; } = new FeatureCollection();
public void Start<TContext>(IHttpApplication<TContext> application)
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -48,7 +49,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "MissingStartupAssembly");
}
}
@ -61,7 +62,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupStaticCtorThrows>().Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "Exception from static constructor");
}
}
@ -74,7 +75,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
}
}
@ -87,7 +88,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupThrowTypeLoadException>().Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "Message from the LoaderException</div>");
}
}
@ -100,7 +101,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
using (host)
{
host.Start();
await host.StartAsync();
var services = host.Services.GetServices<IApplicationLifetime>();
Assert.NotNull(services);
Assert.NotEmpty(services);
@ -110,7 +111,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void DefaultObjectPoolProvider_IsRegistered()
public async Task DefaultObjectPoolProvider_IsRegistered()
{
var server = new TestServer();
var host = CreateWebHostBuilder()
@ -119,7 +120,7 @@ namespace Microsoft.AspNetCore.Hosting
.Build();
using (host)
{
host.Start();
await host.StartAsync();
Assert.IsType<DefaultObjectPoolProvider>(host.Services.GetService<ObjectPoolProvider>());
}
}
@ -132,7 +133,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "Exception from ConfigureServices");
}
}
@ -145,7 +146,7 @@ namespace Microsoft.AspNetCore.Hosting
var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
using (host)
{
host.Start();
await host.StartAsync();
await AssertResponseContains(server.RequestDelegate, "Exception from Configure");
}
}
@ -810,7 +811,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue()
public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue()
{
var provider = new TestLoggerProvider();
var builder = CreateWebHostBuilder()
@ -825,7 +826,7 @@ namespace Microsoft.AspNetCore.Hosting
using (var host = builder.Build())
{
host.Start();
await host.StartAsync();
var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
Assert.NotNull(context);
}
@ -879,7 +880,7 @@ namespace Microsoft.AspNetCore.Hosting
}
public void Start<TContext>(IHttpApplication<TContext> application)
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
RequestDelegate = async ctx =>
{
@ -895,6 +896,13 @@ namespace Microsoft.AspNetCore.Hosting
}
application.DisposeContext(httpContext, null);
};
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

View File

@ -23,40 +23,12 @@ using Xunit;
namespace Microsoft.AspNetCore.Hosting
{
public class WebHostTests : IServer
public class WebHostTests
{
private readonly IList<StartInstance> _startInstances = new List<StartInstance>();
private IFeatureCollection _featuresSupportedByThisHost = NewFeatureCollection();
public IFeatureCollection Features
{
get
{
var features = new FeatureCollection();
foreach (var feature in _featuresSupportedByThisHost)
{
features[feature.Key] = feature.Value;
}
return features;
}
}
static IFeatureCollection NewFeatureCollection()
{
var stub = new StubFeatures();
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(stub);
features.Set<IHttpResponseFeature>(stub);
features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
return features;
}
[Fact]
public void WebHostThrowsWithNoServer()
public async Task WebHostThrowsWithNoServer()
{
var ex = Assert.Throws<InvalidOperationException>(() => CreateBuilder().Build().Start());
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => CreateBuilder().Build().StartAsync());
Assert.Equal("No service for type 'Microsoft.AspNetCore.Hosting.Server.IServer' has been registered.", ex.Message);
}
@ -67,11 +39,11 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void NoDefaultAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
public async Task NoDefaultAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
{
using (var host = CreateBuilder().UseServer(this).Build())
using (var host = CreateBuilder().UseFakeServer().Build())
{
host.Start();
await host.StartAsync();
var serverAddressesFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
Assert.False(serverAddressesFeature.Addresses.Any());
Assert.False(serverAddressesFeature.PreferHostingUrls);
@ -79,7 +51,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void UsesLegacyConfigurationForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
public async Task UsesLegacyConfigurationForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
{
var data = new Dictionary<string, string>
{
@ -88,9 +60,9 @@ namespace Microsoft.AspNetCore.Hosting
var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
using (var host = CreateBuilder(config).UseServer(this).Build())
using (var host = CreateBuilder(config).UseFakeServer().Build())
{
host.Start();
await host.StartAsync();
var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
Assert.Equal("http://localhost:5002", serverAddressFeature.Addresses.First());
Assert.False(serverAddressFeature.PreferHostingUrls);
@ -107,7 +79,7 @@ namespace Microsoft.AspNetCore.Hosting
var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
using (var host = CreateBuilder(config).UseServer(this).Build())
using (var host = CreateBuilder(config).UseFakeServer().Build())
{
host.Start();
var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
@ -117,7 +89,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void UsesNewConfigurationOverLegacyConfigForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
public async Task UsesNewConfigurationOverLegacyConfigForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
{
var data = new Dictionary<string, string>
{
@ -127,9 +99,9 @@ namespace Microsoft.AspNetCore.Hosting
var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
using (var host = CreateBuilder(config).UseServer(this).Build())
using (var host = CreateBuilder(config).UseFakeServer().Build())
{
host.Start();
await host.StartAsync();
var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
Assert.Equal("http://localhost:5009", serverAddressFeature.Addresses.First());
Assert.False(serverAddressFeature.PreferHostingUrls);
@ -139,7 +111,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void DoNotPreferHostingUrlsWhenNoAddressConfigured()
{
using (var host = CreateBuilder().UseServer(this).PreferHostingUrls(true).Build())
using (var host = CreateBuilder().UseFakeServer().PreferHostingUrls(true).Build())
{
host.Start();
var serverAddressesFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
@ -149,7 +121,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void PreferHostingUrlsWhenAddressIsConfigured()
public async Task PreferHostingUrlsWhenAddressIsConfigured()
{
var data = new Dictionary<string, string>
{
@ -158,9 +130,9 @@ namespace Microsoft.AspNetCore.Hosting
var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
using (var host = CreateBuilder(config).UseServer(this).PreferHostingUrls(true).Build())
using (var host = CreateBuilder(config).UseFakeServer().PreferHostingUrls(true).Build())
{
host.Start();
await host.StartAsync();
Assert.True(host.ServerFeatures.Get<IServerAddressesFeature>().PreferHostingUrls);
}
}
@ -169,17 +141,18 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostCanBeStarted()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Start())
{
var server = (FakeServer)host.Services.GetRequiredService<IServer>();
Assert.NotNull(host);
Assert.Equal(1, _startInstances.Count);
Assert.Equal(0, _startInstances[0].DisposeCalls);
Assert.Equal(1, server.StartInstances.Count);
Assert.Equal(0, server.StartInstances[0].DisposeCalls);
host.Dispose();
Assert.Equal(0, _startInstances[0].DisposeCalls);
Assert.Equal(1, server.StartInstances[0].DisposeCalls);
}
}
@ -187,28 +160,29 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostShutsDownWhenTokenTriggers()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Build())
{
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
var server = (FakeServer)host.Services.GetRequiredService<IServer>();
var cts = new CancellationTokenSource();
Task.Run(() => host.Run(cts.Token));
var runInBackground = host.RunAsync(cts.Token);
// Wait on the host to be started
lifetime.ApplicationStarted.WaitHandle.WaitOne();
Assert.Equal(1, _startInstances.Count);
Assert.Equal(0, _startInstances[0].DisposeCalls);
Assert.Equal(1, server.StartInstances.Count);
Assert.Equal(0, server.StartInstances[0].DisposeCalls);
cts.Cancel();
// Wait on the host to shutdown
lifetime.ApplicationStopped.WaitHandle.WaitOne();
Assert.Equal(0, _startInstances[0].DisposeCalls);
Assert.Equal(1, server.StartInstances[0].DisposeCalls);
}
}
@ -216,7 +190,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Build())
{
@ -251,9 +225,9 @@ namespace Microsoft.AspNetCore.Hosting
applicationStoppedEvent.Set();
});
var runHostAndVerifyApplicationStopped = Task.Run(() =>
var runHostAndVerifyApplicationStopped = Task.Run(async () =>
{
host.Run();
await host.RunAsync();
// Check whether the applicationStoppingEvent has been set
applicationStoppedCompletedBeforeRunCompleted = applicationStoppedEvent.IsSet;
});
@ -275,10 +249,10 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHostDisposesServiceProvider()
public async Task WebHostDisposesServiceProvider()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(s =>
{
s.AddTransient<IFakeService, FakeService>();
@ -287,7 +261,7 @@ namespace Microsoft.AspNetCore.Hosting
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.Build())
{
host.Start();
await host.StartAsync();
var singleton = (FakeService)host.Services.GetService<IFakeSingletonService>();
var transient = (FakeService)host.Services.GetService<IFakeService>();
@ -295,6 +269,11 @@ namespace Microsoft.AspNetCore.Hosting
Assert.False(singleton.Disposed);
Assert.False(transient.Disposed);
await host.StopAsync();
Assert.False(singleton.Disposed);
Assert.False(transient.Disposed);
host.Dispose();
Assert.True(singleton.Disposed);
@ -303,26 +282,26 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHostNotifiesApplicationStarted()
public async Task WebHostNotifiesApplicationStarted()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.Build())
{
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested);
host.Start();
await host.StartAsync();
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
}
}
[Fact]
public void WebHostNotifiesAllIApplicationLifetimeCallbacksEvenIfTheyThrow()
public async Task WebHostNotifiesAllIApplicationLifetimeCallbacksEvenIfTheyThrow()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.Build())
{
var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
@ -331,7 +310,7 @@ namespace Microsoft.AspNetCore.Hosting
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
var stopped = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopped);
host.Start();
await host.StartAsync();
Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
Assert.True(started.All(s => s));
host.Dispose();
@ -341,13 +320,13 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHostNotifiesAllIApplicationLifetimeEventsCallbacksEvenIfTheyThrow()
public async Task WebHostNotifiesAllIApplicationLifetimeEventsCallbacksEvenIfTheyThrow()
{
bool[] events1 = null;
bool[] events2 = null;
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
events1 = RegisterCallbacksThatThrow(services);
@ -355,7 +334,7 @@ namespace Microsoft.AspNetCore.Hosting
})
.Build())
{
host.Start();
await host.StartAsync();
Assert.True(events1[0]);
Assert.True(events2[0]);
host.Dispose();
@ -365,12 +344,13 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHostStopApplicationDoesNotFireStopOnHostedService()
public async Task WebHostStopApplicationDoesNotFireStopOnHostedService()
{
var stoppingCalls = 0;
var disposingCalls = 0;
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
Action started = () =>
@ -382,24 +362,32 @@ namespace Microsoft.AspNetCore.Hosting
stoppingCalls++;
};
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping));
Action disposing = () =>
{
disposingCalls++;
};
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
})
.Build())
{
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
lifetime.StopApplication();
host.Start();
await host.StartAsync();
Assert.Equal(0, stoppingCalls);
Assert.Equal(0, disposingCalls);
}
Assert.Equal(1, stoppingCalls);
Assert.Equal(1, disposingCalls);
}
[Fact]
public void HostedServiceCanInjectApplicationLifetime()
public async Task HostedServiceCanInjectApplicationLifetime()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
services.AddSingleton<IHostedService, TestHostedService>();
@ -409,19 +397,21 @@ namespace Microsoft.AspNetCore.Hosting
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
lifetime.StopApplication();
host.Start();
await host.StartAsync();
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
Assert.True(svc.StartCalled);
host.Dispose();
await host.StopAsync();
Assert.True(svc.StopCalled);
host.Dispose();
}
}
[Fact]
public void HostedServiceStartNotCalledIfWebHostNotStarted()
public async Task HostedServiceStartNotCalledIfWebHostNotStarted()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
services.AddSingleton<IHostedService, TestHostedService>();
@ -433,19 +423,23 @@ namespace Microsoft.AspNetCore.Hosting
var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
Assert.False(svc.StartCalled);
await host.StopAsync();
Assert.False(svc.StopCalled);
host.Dispose();
Assert.False(svc.StopCalled);
Assert.True(svc.DisposeCalled);
}
}
[Fact]
public void WebHostDisposeApplicationFiresStopOnHostedService()
public async Task WebHostStopApplicationFiresStopOnHostedService()
{
var stoppingCalls = 0;
var startedCalls = 0;
var disposingCalls = 0;
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
Action started = () =>
@ -458,28 +452,90 @@ namespace Microsoft.AspNetCore.Hosting
stoppingCalls++;
};
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping));
Action disposing = () =>
{
disposingCalls++;
};
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
})
.Build())
{
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
host.Start();
Assert.Equal(0, startedCalls);
await host.StartAsync();
Assert.Equal(1, startedCalls);
Assert.Equal(0, stoppingCalls);
Assert.Equal(0, disposingCalls);
await host.StopAsync();
Assert.Equal(1, startedCalls);
Assert.Equal(1, stoppingCalls);
Assert.Equal(0, disposingCalls);
host.Dispose();
Assert.Equal(1, startedCalls);
Assert.Equal(1, stoppingCalls);
Assert.Equal(1, disposingCalls);
}
}
[Fact]
public void WebHostNotifiesAllIHostedServicesAndIApplicationLifetimeCallbacksEvenIfTheyThrow()
public async Task WebHostDisposeApplicationFiresStopOnHostedService()
{
var stoppingCalls = 0;
var startedCalls = 0;
var disposingCalls = 0;
using (var host = CreateBuilder()
.UseFakeServer()
.ConfigureServices(services =>
{
Action started = () =>
{
startedCalls++;
};
Action stopping = () =>
{
stoppingCalls++;
};
Action disposing = () =>
{
disposingCalls++;
};
services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
})
.Build())
{
var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
Assert.Equal(0, startedCalls);
await host.StartAsync();
Assert.Equal(1, startedCalls);
Assert.Equal(0, stoppingCalls);
Assert.Equal(0, disposingCalls);
host.Dispose();
Assert.Equal(1, stoppingCalls);
Assert.Equal(1, disposingCalls);
}
}
[Fact]
public async Task WebHostNotifiesAllIHostedServicesAndIApplicationLifetimeCallbacksEvenIfTheyThrow()
{
bool[] events1 = null;
bool[] events2 = null;
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
events1 = RegisterCallbacksThatThrow(services);
@ -492,7 +548,7 @@ namespace Microsoft.AspNetCore.Hosting
var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
host.Start();
await host.StartAsync();
Assert.True(events1[0]);
Assert.True(events2[0]);
Assert.True(started.All(s => s));
@ -504,15 +560,15 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHostInjectsHostingEnvironment()
public async Task WebHostInjectsHostingEnvironment()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup("Microsoft.AspNetCore.Hosting.Tests")
.UseEnvironment("WithHostingEnvironment")
.Build())
{
host.Start();
await host.StartAsync();
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal("Changed", env.EnvironmentName);
}
@ -526,7 +582,7 @@ namespace Microsoft.AspNetCore.Hosting
{
services.AddTransient<IStartup, TestStartup>();
})
.UseServer(this)
.UseFakeServer()
.UseStartup("Microsoft.AspNetCore.Hosting.Tests");
Assert.Throws<NotImplementedException>(() => builder.Build());
@ -535,7 +591,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void CanCreateApplicationServicesWithAddedServices()
{
using (var host = CreateBuilder().UseServer(this).ConfigureServices(services => services.AddOptions()).Build())
using (var host = CreateBuilder().UseFakeServer().ConfigureServices(services => services.AddOptions()).Build())
{
Assert.NotNull(host.Services.GetRequiredService<IOptions<object>>());
}
@ -547,7 +603,7 @@ namespace Microsoft.AspNetCore.Hosting
// Verify ordering
var configureOrder = 0;
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.ConfigureServices(services =>
{
services.AddTransient<IStartupFilter>(serviceProvider => new TestFilter(
@ -593,7 +649,7 @@ namespace Microsoft.AspNetCore.Hosting
[Fact]
public void EnvDefaultsToProductionIfNoConfig()
{
using (var host = CreateBuilder().UseServer(this).Build())
using (var host = CreateBuilder().UseFakeServer().Build())
{
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
@ -612,7 +668,7 @@ namespace Microsoft.AspNetCore.Hosting
.AddInMemoryCollection(vals);
var config = builder.Build();
using (var host = CreateBuilder(config).UseServer(this).Build())
using (var host = CreateBuilder(config).UseFakeServer().Build())
{
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal("Staging", env.EnvironmentName);
@ -631,7 +687,7 @@ namespace Microsoft.AspNetCore.Hosting
.AddInMemoryCollection(vals);
var config = builder.Build();
using (var host = CreateBuilder(config).UseServer(this).Build())
using (var host = CreateBuilder(config).UseFakeServer().Build())
{
var env = host.Services.GetService<IHostingEnvironment>();
Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
@ -640,11 +696,11 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void IsEnvironment_Extension_Is_Case_Insensitive()
public async Task IsEnvironment_Extension_Is_Case_Insensitive()
{
using (var host = CreateBuilder().UseServer(this).Build())
using (var host = CreateBuilder().UseFakeServer().Build())
{
host.Start();
await host.StartAsync();
var env = host.Services.GetRequiredService<IHostingEnvironment>();
Assert.True(env.IsEnvironment(EnvironmentName.Production));
Assert.True(env.IsEnvironment("producTion"));
@ -652,7 +708,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHost_CreatesDefaultRequestIdentifierFeature_IfNotPresent()
public async Task WebHost_CreatesDefaultRequestIdentifierFeature_IfNotPresent()
{
// Arrange
HttpContext httpContext = null;
@ -665,7 +721,7 @@ namespace Microsoft.AspNetCore.Hosting
using (var host = CreateHost(requestDelegate))
{
// Act
host.Start();
await host.StartAsync();
// Assert
Assert.NotNull(httpContext);
@ -676,7 +732,7 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHost_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent()
public async Task WebHost_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent()
{
// Arrange
HttpContext httpContext = null;
@ -686,12 +742,18 @@ namespace Microsoft.AspNetCore.Hosting
return Task.FromResult(0);
});
var requestIdentifierFeature = new StubHttpRequestIdentifierFeature();
_featuresSupportedByThisHost[typeof(IHttpRequestIdentifierFeature)] = requestIdentifierFeature;
using (var host = CreateHost(requestDelegate))
{
var server = (FakeServer)host.Services.GetRequiredService<IServer>();
server.CreateRequestFeatures = () =>
{
var features = FakeServer.NewFeatureCollection();
features.Set<IHttpRequestIdentifierFeature>(requestIdentifierFeature);
return features;
};
// Act
host.Start();
await host.StartAsync();
// Assert
Assert.NotNull(httpContext);
@ -700,14 +762,14 @@ namespace Microsoft.AspNetCore.Hosting
}
[Fact]
public void WebHost_InvokesConfigureMethodsOnlyOnce()
public async Task WebHost_InvokesConfigureMethodsOnlyOnce()
{
using (var host = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup<CountStartup>()
.Build())
{
host.Start();
await host.StartAsync();
var services = host.Services;
var services2 = host.Services;
Assert.Equal(1, CountStartup.ConfigureCount);
@ -735,7 +797,7 @@ namespace Microsoft.AspNetCore.Hosting
public void WebHost_ThrowsForBadConfigureServiceSignature()
{
var builder = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.UseStartup<BadConfigureServicesStartup>();
var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
@ -751,7 +813,7 @@ namespace Microsoft.AspNetCore.Hosting
private IWebHost CreateHost(RequestDelegate requestDelegate)
{
var builder = CreateBuilder()
.UseServer(this)
.UseFakeServer()
.Configure(
appBuilder =>
{
@ -782,7 +844,7 @@ namespace Microsoft.AspNetCore.Hosting
throw new InvalidOperationException();
};
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping));
services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping, () => { }));
return events;
}
@ -802,35 +864,7 @@ namespace Microsoft.AspNetCore.Hosting
return signals;
}
public void Start<TContext>(IHttpApplication<TContext> application)
{
var startInstance = new StartInstance();
_startInstances.Add(startInstance);
var context = application.CreateContext(Features);
try
{
application.ProcessRequestAsync(context);
}
catch (Exception ex)
{
application.DisposeContext(context, ex);
throw;
}
application.DisposeContext(context, null);
}
public void Dispose()
{
if (_startInstances != null)
{
foreach (var startInstance in _startInstances)
{
startInstance.Dispose();
}
}
}
private class TestHostedService : IHostedService
private class TestHostedService : IHostedService, IDisposable
{
private readonly IApplicationLifetime _lifetime;
@ -841,6 +875,7 @@ namespace Microsoft.AspNetCore.Hosting
public bool StartCalled { get; set; }
public bool StopCalled { get; set; }
public bool DisposeCalled { get; set; }
public void Start()
{
@ -851,34 +886,117 @@ namespace Microsoft.AspNetCore.Hosting
{
StopCalled = true;
}
public void Dispose()
{
DisposeCalled = true;
}
}
private class DelegateHostedService : IHostedService
private class DelegateHostedService : IHostedService, IDisposable
{
private readonly Action _started;
private readonly Action _stopping;
private readonly Action _disposing;
public DelegateHostedService(Action started, Action stopping)
public DelegateHostedService(Action started, Action stopping, Action disposing)
{
_started = started;
_stopping = stopping;
_disposing = disposing;
}
public void Start() => _started();
public void Stop() => _stopping();
public void Dispose() => _disposing();
}
private class StartInstance : IDisposable
public class StartInstance : IDisposable
{
public int StopCalls { get; set; }
public int DisposeCalls { get; set; }
public void Stop()
{
StopCalls += 1;
}
public void Dispose()
{
DisposeCalls += 1;
}
}
public class FakeServer : IServer
{
public FakeServer()
{
Features = new FeatureCollection();
Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
}
public IList<StartInstance> StartInstances { get; } = new List<StartInstance>();
public Func<IFeatureCollection> CreateRequestFeatures { get; set; } = NewFeatureCollection;
public IFeatureCollection Features { get; }
public static IFeatureCollection NewFeatureCollection()
{
var stub = new StubFeatures();
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(stub);
features.Set<IHttpResponseFeature>(stub);
return features;
}
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
var startInstance = new StartInstance();
StartInstances.Add(startInstance);
var context = application.CreateContext(CreateRequestFeatures());
try
{
application.ProcessRequestAsync(context);
}
catch (Exception ex)
{
application.DisposeContext(context, ex);
throw;
}
application.DisposeContext(context, null);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
if (StartInstances != null)
{
foreach (var startInstance in StartInstances)
{
startInstance.Stop();
}
}
return Task.CompletedTask;
}
public void Dispose()
{
if (StartInstances != null)
{
foreach (var startInstance in StartInstances)
{
startInstance.Dispose();
}
}
}
}
private class TestStartup : IStartup
{
public void Configure(IApplicationBuilder app)
@ -1040,4 +1158,12 @@ namespace Microsoft.AspNetCore.Hosting
public string TraceIdentifier { get; set; }
}
}
public static class TestServerWebHostExtensions
{
public static IWebHostBuilder UseFakeServer(this IWebHostBuilder builder)
{
return builder.ConfigureServices(services => services.AddSingleton<IServer, WebHostTests.FakeServer>());
}
}
}