Skip to content

Commit eb3e0f7

Browse files
committed
Implement workaround for Aspire dashboard launching
1 parent 4dda672 commit eb3e0f7

File tree

2 files changed

+44
-7
lines changed

2 files changed

+44
-7
lines changed

src/BuiltInTools/dotnet-watch/Browser/BrowserConnector.cs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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 = [];
@@ -117,6 +121,10 @@ public bool TryGetRefreshServer(ProjectGraphNode projectNode, [NotNullWhen(true)
117121

118122
bool matchFound = false;
119123

124+
// Workaround for Aspire dashboard launching: scan for "Login to the dashboard at " prefix in the output and use the URL.
125+
// TODO: Share launch profile processing logic as implemented in VS with dotnet-run and implement browser launching there.
126+
var isAspireHost = projectNode.GetCapabilities().Contains(AspireServiceFactory.AppHostProjectCapability);
127+
120128
return handler;
121129

122130
void handler(OutputLine line)
@@ -129,7 +137,7 @@ void handler(OutputLine line)
129137
return;
130138
}
131139

132-
var match = s_nowListeningRegex.Match(line.Content);
140+
var match = (isAspireHost ? s_aspireDashboardUrlRegex : s_nowListeningRegex).Match(line.Content);
133141
if (!match.Success)
134142
{
135143
return;
@@ -141,7 +149,8 @@ void handler(OutputLine line)
141149
if (projectAddedToAttemptedSet)
142150
{
143151
// first iteration:
144-
LaunchBrowser(launchProfile, match.Groups["url"].Value, server);
152+
var launchUrl = GetLaunchUrl(launchProfile.LaunchUrl, match.Groups["url"].Value);
153+
LaunchBrowser(launchUrl, server);
145154
}
146155
else if (server != null)
147156
{
@@ -153,10 +162,15 @@ void handler(OutputLine line)
153162
}
154163
}
155164

156-
private void LaunchBrowser(LaunchSettingsProfile launchProfile, string launchUrl, BrowserRefreshServer? server)
165+
public static string GetLaunchUrl(string? profileLaunchUrl, string outputLaunchUrl)
166+
=> string.IsNullOrWhiteSpace(profileLaunchUrl) ? outputLaunchUrl :
167+
Uri.TryCreate(profileLaunchUrl, UriKind.Absolute, out _) ? profileLaunchUrl :
168+
Uri.TryCreate(outputLaunchUrl, UriKind.Absolute, out var launchUri) ? new Uri(launchUri, profileLaunchUrl).ToString() :
169+
outputLaunchUrl;
170+
171+
private void LaunchBrowser(string launchUrl, BrowserRefreshServer? server)
157172
{
158-
var launchPath = launchProfile.LaunchUrl;
159-
var fileName = Uri.TryCreate(launchPath, UriKind.Absolute, out _) ? launchPath : launchUrl + "/" + launchPath;
173+
var fileName = launchUrl;
160174

161175
var args = string.Empty;
162176
if (EnvironmentVariables.BrowserPath is { } browserPath)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
namespace Microsoft.DotNet.Watch.UnitTests;
7+
8+
public class BrowserConnectorTests
9+
{
10+
[Theory]
11+
[InlineData(null, "https://localhost:1234", "https://localhost:1234")]
12+
[InlineData("", "https://localhost:1234", "https://localhost:1234")]
13+
[InlineData(" ", "https://localhost:1234", "https://localhost:1234")]
14+
[InlineData("", "a/b", "a/b")]
15+
[InlineData("x/y", "a/b", "a/b")]
16+
[InlineData("a/b?X=1", "https://localhost:1234", "https://localhost:1234/a/b?X=1")]
17+
[InlineData("https://localhost:1000/a/b", "https://localhost:1234", "https://localhost:1000/a/b")]
18+
[InlineData("https://localhost:1000/x/y?z=u", "https://localhost:1234/a?b=c", "https://localhost:1000/x/y?z=u")]
19+
public void GetLaunchUrl(string? profileLaunchUrl, string outputLaunchUrl, string expected)
20+
{
21+
Assert.Equal(expected, BrowserConnector.GetLaunchUrl(profileLaunchUrl, outputLaunchUrl));
22+
}
23+
}

0 commit comments

Comments
 (0)