11// Licensed to the .NET Foundation under one or more agreements.
22// The .NET Foundation licenses this file to you under the MIT license.
33
4- using Microsoft . Extensions . Logging ;
5- using Microsoft . Extensions . Options ;
6- using System ;
74using System . Diagnostics ;
8- using System . IO ;
95using System . Net . Http ;
106using 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 ;
1310
1411namespace Microsoft . AspNetCore . SpaProxy
1512{
@@ -25,16 +22,16 @@ internal class SpaProxyLaunchManager : IDisposable
2522
2623 public SpaProxyLaunchManager (
2724 ILogger < SpaProxyLaunchManager > logger ,
25+ IHostApplicationLifetime appLifetime ,
2826 IOptions < SpaDevelopmentServerOptions > options )
2927 {
3028 _options = options . Value ;
3129 _logger = logger ;
30+ appLifetime . ApplicationStopping . Register ( ( ) => Dispose ( true ) ) ;
3231 }
3332
3433 public void StartInBackground ( CancellationToken cancellationToken )
3534 {
36- _logger . LogInformation ( $ "No SPA development server running at { _options . ServerUrl } found.") ;
37-
3835 // We are not waiting for the SPA proxy to launch, instead we are going to rely on a piece of
3936 // middleware to display an HTML document while the SPA proxy is not ready, refresh every three
4037 // seconds and redirect to the SPA proxy url once it is ready.
@@ -45,6 +42,7 @@ public void StartInBackground(CancellationToken cancellationToken)
4542 {
4643 if ( _launchTask == null )
4744 {
45+ _logger . LogInformation ( $ "No SPA development server running at { _options . ServerUrl } found.") ;
4846 _launchTask = UpdateStatus ( StartSpaProcessAndProbeForLiveness ( cancellationToken ) ) ;
4947 }
5048 }
@@ -190,6 +188,46 @@ private void LaunchDevelopmentProxy()
190188 WorkingDirectory = Path . Combine ( AppContext . BaseDirectory , _options . WorkingDirectory )
191189 } ;
192190 _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+ }
193231 }
194232 catch ( Exception exception )
195233 {
@@ -199,7 +237,7 @@ private void LaunchDevelopmentProxy()
199237
200238 public Task StopAsync ( CancellationToken cancellationToken )
201239 {
202- // We don't need to do anything here since Dispose will take care of cleaning up the process if necessary.
240+ Dispose ( true ) ;
203241 return Task . CompletedTask ;
204242 }
205243
0 commit comments