diff --git a/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs b/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs
new file mode 100644
index 0000000000..e52f3d6ab0
--- /dev/null
+++ b/src/Microsoft.AspNetCore.TestHost/HostBuilderTestServerExtensions.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+ public static class HostBuilderTestServerExtensions
+ {
+ ///
+ /// Retrieves the TestServer from the host services.
+ ///
+ ///
+ ///
+ public static TestServer GetTestServer(this IHost host)
+ {
+ return (TestServer)host.Services.GetRequiredService();
+ }
+
+ ///
+ /// Retrieves the test client from the TestServer in the host services.
+ ///
+ ///
+ ///
+ public static HttpClient GetTestClient(this IHost host)
+ {
+ return host.GetTestServer().CreateClient();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.TestHost/TestServer.cs b/src/Microsoft.AspNetCore.TestHost/TestServer.cs
index 398a575d9d..40a3e63f05 100644
--- a/src/Microsoft.AspNetCore.TestHost/TestServer.cs
+++ b/src/Microsoft.AspNetCore.TestHost/TestServer.cs
@@ -6,6 +6,7 @@ using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Internal;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@@ -15,28 +16,48 @@ namespace Microsoft.AspNetCore.TestHost
{
public class TestServer : IServer
{
- private const string ServerName = nameof(TestServer);
private IWebHost _hostInstance;
private bool _disposed = false;
private IHttpApplication _application;
+ ///
+ /// For use with IHostBuilder or IWebHostBuilder.
+ ///
+ public TestServer()
+ : this(new FeatureCollection())
+ {
+ }
+
+ ///
+ /// For use with IHostBuilder or IWebHostBuilder.
+ ///
+ ///
+ public TestServer(IFeatureCollection featureCollection)
+ {
+ Features = featureCollection ?? throw new ArgumentNullException(nameof(featureCollection));
+ }
+
+ ///
+ /// For use with IWebHostBuilder.
+ ///
+ ///
public TestServer(IWebHostBuilder builder)
: this(builder, new FeatureCollection())
{
}
+ ///
+ /// For use with IWebHostBuilder.
+ ///
+ ///
+ ///
public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection)
+ : this(featureCollection)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
- if (featureCollection == null)
- {
- throw new ArgumentNullException(nameof(featureCollection));
- }
-
- Features = featureCollection;
var host = builder.UseServer(this).Build();
host.StartAsync().GetAwaiter().GetResult();
@@ -49,16 +70,22 @@ namespace Microsoft.AspNetCore.TestHost
{
get
{
- return _hostInstance;
+ return _hostInstance
+ ?? throw new InvalidOperationException("The TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available.");
}
}
public IFeatureCollection Features { get; }
+ private IHttpApplication Application
+ {
+ get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured.");
+ }
+
public HttpMessageHandler CreateHandler()
{
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
- return new ClientHandler(pathBase, _application);
+ return new ClientHandler(pathBase, Application);
}
public HttpClient CreateClient()
@@ -69,7 +96,7 @@ namespace Microsoft.AspNetCore.TestHost
public WebSocketClient CreateWebSocketClient()
{
var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
- return new WebSocketClient(pathBase, _application);
+ return new WebSocketClient(pathBase, Application);
}
///
@@ -93,7 +120,7 @@ namespace Microsoft.AspNetCore.TestHost
throw new ArgumentNullException(nameof(configureContext));
}
- var builder = new HttpContextBuilder(_application);
+ var builder = new HttpContextBuilder(Application);
builder.Configure(context =>
{
var request = context.Request;
diff --git a/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs b/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs
index cccf19cdd5..308ac86cd4 100644
--- a/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs
+++ b/src/Microsoft.AspNetCore.TestHost/WebHostBuilderExtensions.cs
@@ -6,12 +6,21 @@ using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.TestHost
{
public static class WebHostBuilderExtensions
{
+ public static IWebHostBuilder UseTestServer(this IWebHostBuilder builder)
+ {
+ return builder.ConfigureServices(services =>
+ {
+ services.AddSingleton();
+ });
+ }
+
public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder webHostBuilder, Action servicesConfiguration)
{
if (webHostBuilder == null)
diff --git a/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs b/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs
index 8b1c4d3494..b4753d7201 100644
--- a/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs
+++ b/src/Microsoft.Extensions.Hosting.Abstractions/HostingAbstractionsHostBuilderExtensions.cs
@@ -2,20 +2,32 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
+using System.Threading.Tasks;
namespace Microsoft.Extensions.Hosting
{
public static class HostingAbstractionsHostBuilderExtensions
{
///
- /// Start the host and listen on the specified urls.
+ /// Builds and starts the host.
///
/// The to start.
- /// The .
+ /// The started .
public static IHost Start(this IHostBuilder hostBuilder)
+ {
+ return hostBuilder.StartAsync().GetAwaiter().GetResult();
+ }
+
+ ///
+ /// Builds and starts the host.
+ ///
+ /// The to start.
+ ///
+ /// The started .
+ public static async Task StartAsync(this IHostBuilder hostBuilder, CancellationToken cancellationToken = default)
{
var host = hostBuilder.Build();
- host.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
+ await host.StartAsync(cancellationToken);
return host;
}
}
diff --git a/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj b/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj
index 25b877467c..f62aae9cec 100644
--- a/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj
+++ b/test/Microsoft.AspNetCore.TestHost.Tests/Microsoft.AspNetCore.TestHost.Tests.csproj
@@ -6,6 +6,7 @@
+
diff --git a/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs b/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs
index a4175a75cb..cc789962c7 100644
--- a/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs
+++ b/test/Microsoft.AspNetCore.TestHost.Tests/TestServerTests.cs
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DiagnosticAdapter;
+using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
using Xunit;
@@ -23,6 +24,56 @@ namespace Microsoft.AspNetCore.TestHost
{
public class TestServerTests
{
+ [Fact]
+ public async Task GenericRawCreate()
+ {
+ var server = new TestServer();
+ var host = new HostBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder
+ .UseServer(server)
+ .Configure(app => { });
+ })
+ .Build();
+ await host.StartAsync();
+
+ var response = await server.CreateClient().GetAsync("/");
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GenericCreateAndStartHost_GetTestServer()
+ {
+ var host = await new HostBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder
+ .UseTestServer()
+ .Configure(app => { });
+ })
+ .StartAsync();
+
+ var response = await host.GetTestServer().CreateClient().GetAsync("/");
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task GenericCreateAndStartHost_GetTestClient()
+ {
+ var host = await new HostBuilder()
+ .ConfigureWebHost(webBuilder =>
+ {
+ webBuilder
+ .UseTestServer()
+ .Configure(app => { });
+ })
+ .StartAsync();
+
+ var response = await host.GetTestClient().GetAsync("/");
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
[Fact]
public void CreateWithDelegate()
{
@@ -31,6 +82,17 @@ namespace Microsoft.AspNetCore.TestHost
new TestServer(new WebHostBuilder().Configure(app => { }));
}
+ [Fact]
+ public void CreateWithDelegate_DI()
+ {
+ var builder = new WebHostBuilder()
+ .Configure(app => { })
+ .UseTestServer();
+
+ var host = builder.Build();
+ host.Start();
+ }
+
[Fact]
public void DoesNotCaptureStartupErrorsByDefault()
{