@@ -14,10 +14,14 @@ internal sealed partial class BrowserConnector(DotNetWatchContext context) : IAs
1414 // This needs to be in sync with the version BrowserRefreshMiddleware is compiled against.
1515 private static readonly Version s_minimumSupportedVersion = Versions . Version6_0 ;
1616
17- private static readonly Regex s_nowListeningRegex = s_nowListeningOnRegex ( ) ;
17+ private static readonly Regex s_nowListeningRegex = GetNowListeningOnRegex ( ) ;
18+ private static readonly Regex s_aspireDashboardUrlRegex = GetAspireDashboardUrlRegex ( ) ;
1819
1920 [ GeneratedRegex ( @"Now listening on: (?<url>.*)\s*$" , RegexOptions . Compiled ) ]
20- private static partial Regex s_nowListeningOnRegex ( ) ;
21+ private static partial Regex GetNowListeningOnRegex ( ) ;
22+
23+ [ GeneratedRegex ( @"Login to the dashboard at (?<url>.*)\s*$" , RegexOptions . Compiled ) ]
24+ private static partial Regex GetAspireDashboardUrlRegex ( ) ;
2125
2226 private readonly object _serversGuard = new ( ) ;
2327 private readonly Dictionary < ProjectGraphNode , BrowserRefreshServer ? > _servers = [ ] ;
@@ -115,6 +119,10 @@ public bool TryGetRefreshServer(ProjectGraphNode projectNode, [NotNullWhen(true)
115119
116120 bool matchFound = false ;
117121
122+ // Workaround for Aspire dashboard launching: scan for "Login to the dashboard at " prefix in the output and use the URL.
123+ // TODO: Share launch profile processing logic as implemented in VS with dotnet-run and implement browser launching there.
124+ var isAspireHost = projectNode . GetCapabilities ( ) . Contains ( AspireServiceFactory . AppHostProjectCapability ) ;
125+
118126 return handler ;
119127
120128 void handler ( OutputLine line )
@@ -127,7 +135,7 @@ void handler(OutputLine line)
127135 return ;
128136 }
129137
130- var match = s_nowListeningRegex . Match ( line . Content ) ;
138+ var match = ( isAspireHost ? s_aspireDashboardUrlRegex : s_nowListeningRegex ) . Match ( line . Content ) ;
131139 if ( ! match . Success )
132140 {
133141 return ;
@@ -139,7 +147,8 @@ void handler(OutputLine line)
139147 ImmutableInterlocked . Update ( ref _browserLaunchAttempted , static ( set , projectNode ) => set . Add ( projectNode ) , projectNode ) )
140148 {
141149 // first build iteration of a root project:
142- LaunchBrowser ( launchProfile , match . Groups [ "url" ] . Value , server ) ;
150+ var launchUrl = GetLaunchUrl ( launchProfile . LaunchUrl , match . Groups [ "url" ] . Value ) ;
151+ LaunchBrowser ( launchUrl , server ) ;
143152 }
144153 else if ( server != null )
145154 {
@@ -151,10 +160,15 @@ void handler(OutputLine line)
151160 }
152161 }
153162
154- private void LaunchBrowser ( LaunchSettingsProfile launchProfile , string launchUrl , BrowserRefreshServer ? server )
163+ public static string GetLaunchUrl ( string ? profileLaunchUrl , string outputLaunchUrl )
164+ => string . IsNullOrWhiteSpace ( profileLaunchUrl ) ? outputLaunchUrl :
165+ Uri . TryCreate ( profileLaunchUrl , UriKind . Absolute , out _ ) ? profileLaunchUrl :
166+ Uri . TryCreate ( outputLaunchUrl , UriKind . Absolute , out var launchUri ) ? new Uri ( launchUri , profileLaunchUrl ) . ToString ( ) :
167+ outputLaunchUrl ;
168+
169+ private void LaunchBrowser ( string launchUrl , BrowserRefreshServer ? server )
155170 {
156- var launchPath = launchProfile . LaunchUrl ;
157- var fileName = Uri . TryCreate ( launchPath , UriKind . Absolute , out _ ) ? launchPath : launchUrl + "/" + launchPath ;
171+ var fileName = launchUrl ;
158172
159173 var args = string . Empty ;
160174 if ( EnvironmentVariables . BrowserPath is { } browserPath )
0 commit comments