99using Microsoft . AspNetCore . Hosting ;
1010using Microsoft . AspNetCore . Hosting . Server ;
1111using Microsoft . AspNetCore . Hosting . Server . Features ;
12+ using Microsoft . AspNetCore . Server . Kestrel . Core ;
1213using Microsoft . AspNetCore . TestHost ;
1314using Microsoft . Extensions . Configuration ;
1415using Microsoft . Extensions . DependencyInjection ;
@@ -24,9 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.Testing;
2425/// Typically the Startup or Program classes can be used.</typeparam>
2526public 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 )
0 commit comments