1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
- using Microsoft . Extensions . Logging ;
5
- using Microsoft . Extensions . Options ;
6
- using System ;
7
4
using System . Diagnostics ;
8
- using System . IO ;
9
5
using System . Net . Http ;
10
6
using System . Net . Http . Headers ;
11
- using System . Threading ;
12
- using System . Threading . Tasks ;
7
+ using Microsoft . Extensions . Hosting ;
8
+ using Microsoft . Extensions . Logging ;
9
+ using Microsoft . Extensions . Options ;
13
10
14
11
namespace Microsoft . AspNetCore . SpaProxy
15
12
{
@@ -25,16 +22,16 @@ internal class SpaProxyLaunchManager : IDisposable
25
22
26
23
public SpaProxyLaunchManager (
27
24
ILogger < SpaProxyLaunchManager > logger ,
25
+ IHostApplicationLifetime appLifetime ,
28
26
IOptions < SpaDevelopmentServerOptions > options )
29
27
{
30
28
_options = options . Value ;
31
29
_logger = logger ;
30
+ appLifetime . ApplicationStopping . Register ( ( ) => Dispose ( true ) ) ;
32
31
}
33
32
34
33
public void StartInBackground ( CancellationToken cancellationToken )
35
34
{
36
- _logger . LogInformation ( $ "No SPA development server running at { _options . ServerUrl } found.") ;
37
-
38
35
// We are not waiting for the SPA proxy to launch, instead we are going to rely on a piece of
39
36
// middleware to display an HTML document while the SPA proxy is not ready, refresh every three
40
37
// seconds and redirect to the SPA proxy url once it is ready.
@@ -45,6 +42,7 @@ public void StartInBackground(CancellationToken cancellationToken)
45
42
{
46
43
if ( _launchTask == null )
47
44
{
45
+ _logger . LogInformation ( $ "No SPA development server running at { _options . ServerUrl } found.") ;
48
46
_launchTask = UpdateStatus ( StartSpaProcessAndProbeForLiveness ( cancellationToken ) ) ;
49
47
}
50
48
}
@@ -190,6 +188,46 @@ private void LaunchDevelopmentProxy()
190
188
WorkingDirectory = Path . Combine ( AppContext . BaseDirectory , _options . WorkingDirectory )
191
189
} ;
192
190
_spaProcess = Process . Start ( info ) ;
191
+ if ( OperatingSystem . IsWindows ( ) && _spaProcess != null )
192
+ {
193
+ var stopScript = $@ "do{{
194
+ try
195
+ {{
196
+ $processId = Get-Process -PID { Environment . ProcessId } -ErrorAction Stop;
197
+ }}catch
198
+ {{
199
+ $processId = $null;
200
+ }}
201
+ Start-Sleep -Seconds 1;
202
+ }}while($processId -ne $null);
203
+
204
+ try
205
+ {{
206
+ taskkill /T /F /PID { _spaProcess . Id } ;
207
+ }}
208
+ catch
209
+ {{
210
+ }}" ;
211
+ var stopScriptInfo = new ProcessStartInfo (
212
+ "powershell.exe" ,
213
+ string . Join ( " " , "-NoProfile" , "-C" , stopScript ) )
214
+ {
215
+ CreateNoWindow = true ,
216
+ WorkingDirectory = Path . Combine ( AppContext . BaseDirectory , _options . WorkingDirectory )
217
+ } ;
218
+
219
+ var stopProcess = Process . Start ( stopScriptInfo ) ;
220
+ if ( stopProcess == null || stopProcess . HasExited )
221
+ {
222
+ _logger . LogWarning ( $ "SPA process shutdown script '{ stopProcess ? . Id } ' failed to start. The SPA proxy might" +
223
+ $ " remain open if the dotnet process is terminated abruptly. Use the operating system command to kill" +
224
+ $ "the process tree for { _spaProcess . Id } ") ;
225
+ }
226
+ else
227
+ {
228
+ _logger . LogDebug ( $ "Watch process '{ stopProcess } ' started.") ;
229
+ }
230
+ }
193
231
}
194
232
catch ( Exception exception )
195
233
{
@@ -199,7 +237,7 @@ private void LaunchDevelopmentProxy()
199
237
200
238
public Task StopAsync ( CancellationToken cancellationToken )
201
239
{
202
- // We don't need to do anything here since Dispose will take care of cleaning up the process if necessary.
240
+ Dispose ( true ) ;
203
241
return Task . CompletedTask ;
204
242
}
205
243
0 commit comments