Skip to content

Commit 5ab8349

Browse files
authored
[wasm][debugger] Support create, debugging and running wasmbrowser template from VS (#75986)
* Support create, debugging and running wasmbrowser template from VS * addings extra line in the end of the file * remove extra spaces * fix compilation error * adding extra line in the end of the file * Addressing @lewing comment.
1 parent 74c589f commit 5ab8349

7 files changed

Lines changed: 120 additions & 10 deletions

File tree

src/mono/wasm/build/WasmApp.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@
115115
<WasmDebugLevel Condition="('$(WasmDebugLevel)' == '' or '$(WasmDebugLevel)' == '0') and ('$(DebuggerSupport)' == 'true' or '$(Configuration)' == 'Debug')">-1</WasmDebugLevel>
116116
</PropertyGroup>
117117

118+
<ItemGroup>
119+
<!-- Allow running/debugging from VS -->
120+
<ProjectCapability Include="DotNetCoreWeb"/>
121+
</ItemGroup>
122+
118123
<PropertyGroup Label="Identify app bundle directory to run from">
119124
<!-- Allow running from custom WasmAppDir -->
120125
<_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)</_AppBundleDirForRunCommand>

src/mono/wasm/host/BrowserHost.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,13 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
7171
debugging: _args.CommonConfig.Debugging);
7272
runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));
7373

74+
var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" };
75+
if (envVars["ASPNETCORE_URLS"] is not null)
76+
urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
77+
7478
(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
7579
_args.ForwardConsoleOutput ?? false,
76-
_args.CommonConfig.HostProperties.WebServerPort,
80+
urls,
7781
token);
7882

7983
string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs);
@@ -84,7 +88,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
8488
await host.WaitForShutdownAsync(token);
8589
}
8690

87-
private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token)
91+
private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token)
8892
{
8993
WasmTestMessagesProcessor? logProcessor = null;
9094
if (forwardConsole)
@@ -100,7 +104,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
100104
ContentRootPath: Path.GetFullPath(appPath),
101105
WebServerUseCors: true,
102106
WebServerUseCrossOriginPolicy: true,
103-
Port: port
107+
Urls: urls
104108
);
105109

106110
(ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token);

src/mono/wasm/host/WebServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public class WebServer
2020
{
2121
internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token)
2222
{
23-
string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" };
23+
string[] urls = options.Urls;
2424

2525
IWebHostBuilder builder = new WebHostBuilder()
2626
.UseKestrel()

src/mono/wasm/host/WebServerOptions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ internal sealed record WebServerOptions
1515
string? ContentRootPath,
1616
bool WebServerUseCors,
1717
bool WebServerUseCrossOriginPolicy,
18-
int Port,
18+
string [] Urls,
1919
string DefaultFileName = "index.html"
2020
);

src/mono/wasm/host/WebServerStartup.cs

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
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 System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.IO;
8+
using System.Net;
49
using System.Net.WebSockets;
10+
using System.Reflection;
11+
using System.Runtime.InteropServices;
12+
using System.Threading.Tasks;
13+
using System.Web;
514
using Microsoft.AspNetCore.Builder;
615
using Microsoft.AspNetCore.Hosting;
716
using Microsoft.AspNetCore.Routing;
817
using Microsoft.AspNetCore.StaticFiles;
918
using Microsoft.Extensions.FileProviders;
1019
using Microsoft.Extensions.Options;
20+
using Microsoft.WebAssembly.Diagnostics;
1121

1222
#nullable enable
1323

@@ -16,9 +26,32 @@ namespace Microsoft.WebAssembly.AppHost;
1626
internal sealed class WebServerStartup
1727
{
1828
private readonly IWebHostEnvironment _hostingEnvironment;
19-
29+
private static readonly object LaunchLock = new object();
30+
private static string LaunchedDebugProxyUrl = "";
2031
public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment;
2132

33+
public static int StartDebugProxy(string devToolsHost)
34+
{
35+
//we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint
36+
//on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value
37+
var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll");
38+
var ownerPid = Environment.ProcessId;
39+
var generateRandomPort = new Random().Next(5000, 5300);
40+
var processStartInfo = new ProcessStartInfo
41+
{
42+
FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""),
43+
Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}",
44+
UseShellExecute = false,
45+
RedirectStandardOutput = true,
46+
};
47+
var debugProxyProcess = Process.Start(processStartInfo);
48+
if (debugProxyProcess is null)
49+
{
50+
throw new InvalidOperationException("Unable to start debug proxy process.");
51+
}
52+
return generateRandomPort;
53+
}
54+
2255
public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> optionsContainer)
2356
{
2457
var provider = new FileExtensionContentTypeProvider();
@@ -73,9 +106,43 @@ public void Configure(IApplicationBuilder app, IOptions<WebServerOptions> option
73106
});
74107
}
75108

76-
// app.UseEndpoints(endpoints =>
77-
// {
78-
// endpoints.MapFallbackToFile(options.DefaultFileName);
79-
// });
109+
app.Map("/debug", app =>
110+
{
111+
app.Run(async (context) =>
112+
{
113+
//debug from VS
114+
var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!);
115+
var browserParam = queryParams.Get("browser");
116+
Uri? browserUrl = null;
117+
var devToolsHost = "http://localhost:9222";
118+
if (browserParam != null)
119+
{
120+
browserUrl = new Uri(browserParam);
121+
devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}";
122+
}
123+
lock (LaunchLock)
124+
{
125+
if (LaunchedDebugProxyUrl == "")
126+
{
127+
LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}";
128+
}
129+
}
130+
var requestPath = context.Request.Path.ToString();
131+
if (requestPath == string.Empty)
132+
{
133+
requestPath = "/";
134+
}
135+
context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}");
136+
await Task.FromResult(0);
137+
});
138+
});
139+
app.UseEndpoints(endpoints =>
140+
{
141+
endpoints.MapGet("/", context =>
142+
{
143+
context.Response.Redirect("index.html", permanent: false);
144+
return Task.CompletedTask;
145+
});
146+
});
80147
}
81148
}

src/mono/wasm/templates/templates/browser/.template.config/template.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"$schema": "http://json.schemastore.org/template",
33
"author": "Microsoft",
44
"classifications": [ "Web", "WebAssembly", "Browser" ],
5+
"generatorVersions": "[1.0.0.0-*)",
56
"identity": "WebAssembly.Browser",
67
"name": "WebAssembly Browser App",
78
"shortName": "wasmbrowser",
@@ -10,5 +11,25 @@
1011
"tags": {
1112
"language": "C#",
1213
"type": "project"
14+
},
15+
"symbols": {
16+
"kestrelHttpPortGenerated": {
17+
"type": "generated",
18+
"generator": "port",
19+
"parameters": {
20+
"low": 5000,
21+
"high": 5300
22+
},
23+
"replaces": "5000"
24+
},
25+
"kestrelHttpsPortGenerated": {
26+
"type": "generated",
27+
"generator": "port",
28+
"parameters": {
29+
"low": 7000,
30+
"high": 7300
31+
},
32+
"replaces": "5001"
33+
}
1334
}
1435
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"profiles": {
3+
"browser.0": {
4+
"commandName": "Project",
5+
"launchBrowser": true,
6+
"environmentVariables": {
7+
"ASPNETCORE_ENVIRONMENT": "Development"
8+
},
9+
"applicationUrl": "https://localhost:5001;http://localhost:5000",
10+
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)