#947 Add IServer.StopAsyc, IWebHost.StopAsync, and make Start async
This commit is contained in:
parent
5f9fa5c009
commit
62f74d5be0
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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>>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue