22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System . Linq ;
5+ using Microsoft . AspNetCore . Builder ;
56using Microsoft . AspNetCore . Hosting . Server ;
67using Microsoft . AspNetCore . Hosting . Server . Features ;
8+ using Microsoft . AspNetCore . Http ;
79using Microsoft . AspNetCore . Http . Features ;
810using Microsoft . Extensions . Configuration ;
911using Microsoft . Extensions . DependencyInjection ;
@@ -128,6 +130,59 @@ public void ReadsUrlsOrPorts(string urls, string httpPorts, string httpsPorts, s
128130 Assert . Equal ( expected , string . Join ( ';' , server . Addresses ) ) ;
129131 }
130132
133+ [ Fact ]
134+ public async Task MultipleConfigureWebHostCallsWithUseStartupLastWins ( )
135+ {
136+ var server = new TestServer ( ) ;
137+
138+ using var host = new HostBuilder ( )
139+ . ConfigureWebHost ( webHostBuilder =>
140+ {
141+ webHostBuilder
142+ . UseServer ( server )
143+ . UseStartup < FirstStartup > ( ) ;
144+ } )
145+ . ConfigureWebHost ( webHostBuilder =>
146+ {
147+ webHostBuilder
148+ . UseStartup < SecondStartup > ( ) ;
149+ } )
150+ . Build ( ) ;
151+
152+ await host . StartAsync ( ) ;
153+ await AssertResponseContains ( server . RequestDelegate , "SecondStartup" ) ;
154+ }
155+
156+ private async Task AssertResponseContains ( RequestDelegate app , string expectedText )
157+ {
158+ var httpContext = new DefaultHttpContext ( ) ;
159+ httpContext . Response . Body = new MemoryStream ( ) ;
160+ await app ( httpContext ) ;
161+ httpContext . Response . Body . Seek ( 0 , SeekOrigin . Begin ) ;
162+ var bodyText = new StreamReader ( httpContext . Response . Body ) . ReadToEnd ( ) ;
163+ Assert . Contains ( expectedText , bodyText ) ;
164+ }
165+
166+ private class FirstStartup
167+ {
168+ public void ConfigureServices ( IServiceCollection services ) { }
169+
170+ public void Configure ( IApplicationBuilder app )
171+ {
172+ app . Run ( context => context . Response . WriteAsync ( "FirstStartup" ) ) ;
173+ }
174+ }
175+
176+ private class SecondStartup
177+ {
178+ public void ConfigureServices ( IServiceCollection services ) { }
179+
180+ public void Configure ( IApplicationBuilder app )
181+ {
182+ app . Run ( context => context . Response . WriteAsync ( "SecondStartup" ) ) ;
183+ }
184+ }
185+
131186 private class TestServer : IServer , IServerAddressesFeature
132187 {
133188 public TestServer ( )
@@ -139,9 +194,31 @@ public TestServer()
139194
140195 public ICollection < string > Addresses { get ; } = new List < string > ( ) ;
141196 public bool PreferHostingUrls { get ; set ; }
197+ public RequestDelegate RequestDelegate { get ; private set ; }
142198
143- public Task StartAsync < TContext > ( IHttpApplication < TContext > application , CancellationToken cancellationToken ) => Task . CompletedTask ;
144- public Task StopAsync ( CancellationToken cancellationToken ) => Task . CompletedTask ;
145199 public void Dispose ( ) { }
200+
201+ public Task StartAsync < TContext > ( IHttpApplication < TContext > application , CancellationToken cancellationToken )
202+ {
203+ // For testing that uses RequestDelegate
204+ RequestDelegate = async ctx =>
205+ {
206+ var httpContext = application . CreateContext ( ctx . Features ) ;
207+ try
208+ {
209+ await application . ProcessRequestAsync ( httpContext ) ;
210+ }
211+ catch ( Exception ex )
212+ {
213+ application . DisposeContext ( httpContext , ex ) ;
214+ throw ;
215+ }
216+ application . DisposeContext ( httpContext , null ) ;
217+ } ;
218+
219+ return Task . CompletedTask ;
220+ }
221+
222+ public Task StopAsync ( CancellationToken cancellationToken ) => Task . CompletedTask ;
146223 }
147224}
0 commit comments