Skip to content

Commit ec5f862

Browse files
committed
- Allow specifying port to be used by Kestrel
- Allow configuring Kestrel options.
1 parent d6f407a commit ec5f862

File tree

3 files changed

+87
-12
lines changed

3 files changed

+87
-12
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#nullable enable
2-
*REMOVED*Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.Server.get -> Microsoft.AspNetCore.TestHost.TestServer!
3-
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.Initialize() -> void
4-
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.Server.get -> Microsoft.AspNetCore.TestHost.TestServer?
2+
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.StartServer() -> void
53
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel() -> void
4+
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel(int port) -> void
5+
Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory<TEntryPoint>.UseKestrel(System.Action<Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions!>! configureKestrelOptions) -> void

src/Mvc/Mvc.Testing/src/WebApplicationFactory.cs

Lines changed: 65 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNetCore.Hosting;
1010
using Microsoft.AspNetCore.Hosting.Server;
1111
using Microsoft.AspNetCore.Hosting.Server.Features;
12+
using Microsoft.AspNetCore.Server.Kestrel.Core;
1213
using Microsoft.AspNetCore.TestHost;
1314
using Microsoft.Extensions.Configuration;
1415
using Microsoft.Extensions.DependencyInjection;
@@ -24,9 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.Testing;
2425
/// Typically the Startup or Program classes can be used.</typeparam>
2526
public partial class WebApplicationFactory<TEntryPoint> : IDisposable, IAsyncDisposable where TEntryPoint : class
2627
{
27-
private bool _useKestrel;
2828
private bool _disposed;
2929
private bool _disposedAsync;
30+
31+
private bool _useKestrel;
32+
private int? _kestrelPort;
33+
private Action<KestrelServerOptions>? _configureKestrelOptions;
34+
3035
private TestServer? _server;
3136
private IHost? _host;
3237
private Action<IWebHostBuilder> _configuration;
@@ -74,12 +79,17 @@ public WebApplicationFactory()
7479
/// <summary>
7580
/// Gets the <see cref="TestServer"/> created by this <see cref="WebApplicationFactory{TEntryPoint}"/>.
7681
/// </summary>
77-
public TestServer? Server
82+
public TestServer Server
7883
{
7984
get
8085
{
81-
Initialize();
82-
return _server;
86+
if (_useKestrel)
87+
{
88+
throw new NotSupportedException();
89+
}
90+
91+
StartServer();
92+
return _server!;
8393
}
8494
}
8595

@@ -90,7 +100,7 @@ public virtual IServiceProvider Services
90100
{
91101
get
92102
{
93-
Initialize();
103+
StartServer();
94104
if (_useKestrel)
95105
{
96106
return _webHost!.Services;
@@ -157,9 +167,55 @@ public void UseKestrel()
157167
_useKestrel = true;
158168
}
159169

160-
private static IWebHost CreateKestrelServer(IWebHostBuilder builder)
170+
/// <summary>
171+
/// Configures the factory to use Kestrel as the server.
172+
/// </summary>
173+
/// <param name="port">The port to listen to when the server starts. Use `0` to allow dynamic port selection.</param>
174+
/// <exception cref="InvalidOperationException">Thrown, if this method is called after the WebHostFactory has been initialized.</exception>
175+
/// <remarks>This method should be called before the factory is initialized either via one of the <see cref="CreateClient()"/> methods
176+
/// or via the <see cref="StartServer"/> method.</remarks>
177+
public void UseKestrel(int port)
161178
{
162-
var host = builder.UseKestrel().Build();
179+
UseKestrel();
180+
181+
this._kestrelPort = port;
182+
}
183+
184+
/// <summary>
185+
/// Configures the factory to use Kestrel as the server.
186+
/// </summary>
187+
/// <param name="configureKestrelOptions">A callback handler that will be used for configuring the server when it starts.</param>
188+
/// <exception cref="InvalidOperationException">Thrown, if this method is called after the WebHostFactory has been initialized.</exception>
189+
/// <remarks>This method should be called before the factory is initialized either via one of the <see cref="CreateClient()"/> methods
190+
/// or via the <see cref="StartServer"/> method.</remarks>
191+
public void UseKestrel(Action<KestrelServerOptions> configureKestrelOptions)
192+
{
193+
UseKestrel();
194+
195+
this._configureKestrelOptions = configureKestrelOptions;
196+
}
197+
198+
private IWebHost CreateKestrelServer(IWebHostBuilder builder)
199+
{
200+
if (_configureKestrelOptions is not null)
201+
{
202+
builder.UseKestrel(_configureKestrelOptions);
203+
}
204+
else
205+
{
206+
builder.UseKestrel();
207+
}
208+
209+
var host = builder.Build();
210+
211+
if (_kestrelPort.HasValue)
212+
{
213+
var saf = host.Services.GetRequiredService<IServerAddressesFeature>();
214+
saf.Addresses.Clear();
215+
saf.Addresses.Add($"http://127.0.0.1:{_kestrelPort}");
216+
saf.PreferHostingUrls = true;
217+
}
218+
163219
host.Start();
164220
return host;
165221
}
@@ -168,7 +224,7 @@ private static IWebHost CreateKestrelServer(IWebHostBuilder builder)
168224
/// Initializes the instance by configurating the host builder.
169225
/// </summary>
170226
/// <exception cref="InvalidOperationException">Thrown if the provided <typeparamref name="TEntryPoint"/> type has no factory method.</exception>
171-
public void Initialize()
227+
public void StartServer()
172228
{
173229
if (_server != null || _webHost != null)
174230
{
@@ -542,7 +598,7 @@ public HttpClient CreateClient(WebApplicationFactoryClientOptions options) =>
542598
/// <returns>The <see cref="HttpClient"/>.</returns>
543599
public HttpClient CreateDefaultClient(params DelegatingHandler[] handlers)
544600
{
545-
Initialize();
601+
StartServer();
546602

547603
HttpClient client;
548604
if (handlers == null || handlers.Length == 0)

src/Mvc/test/Mvc.FunctionalTests/RealServerBackedIntegrationTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,23 @@ public async Task ServerReachableViaGenericHttpClient()
5454
// Assert
5555
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
5656
}
57+
58+
[Fact]
59+
public async Task ServerReachableViaGenericHttpClient_OnSpecificPort()
60+
{
61+
// Arrange
62+
var port = 7777;
63+
var baseAddress = new Uri($"http://localhost:{port}");
64+
65+
// Act
66+
Factory.UseKestrel(port);
67+
Factory.StartServer();
68+
69+
using var client = new HttpClient() { BaseAddress = baseAddress };
70+
71+
using var response = await client.GetAsync("/");
72+
73+
// Assert
74+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
75+
}
5776
}

0 commit comments

Comments
 (0)