diff --git a/src/mono/wasm/build/WasmApp.targets b/src/mono/wasm/build/WasmApp.targets
index 811e858ca839de..fbd16363dd9ea3 100644
--- a/src/mono/wasm/build/WasmApp.targets
+++ b/src/mono/wasm/build/WasmApp.targets
@@ -115,6 +115,11 @@
-1
+
+
+
+
+
<_AppBundleDirForRunCommand Condition="'$(WasmAppDir)' != ''">$(WasmAppDir)
diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs
index bccca9ff4f179a..a592ca7386fff3 100644
--- a/src/mono/wasm/host/BrowserHost.cs
+++ b/src/mono/wasm/host/BrowserHost.cs
@@ -71,9 +71,13 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
debugging: _args.CommonConfig.Debugging);
runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json"));
+ string[] urls = envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls)
+ ? aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
+ : new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" };
+
(ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath,
_args.ForwardConsoleOutput ?? false,
- _args.CommonConfig.HostProperties.WebServerPort,
+ urls,
token);
string[] fullUrls = BuildUrls(serverURLs, _args.AppArgs);
@@ -84,7 +88,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
await host.WaitForShutdownAsync(token);
}
- private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, int port, CancellationToken token)
+ private async Task<(ServerURLs, IWebHost)> StartWebServerAsync(string appPath, bool forwardConsole, string[] urls, CancellationToken token)
{
WasmTestMessagesProcessor? logProcessor = null;
if (forwardConsole)
@@ -100,7 +104,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke
ContentRootPath: Path.GetFullPath(appPath),
WebServerUseCors: true,
WebServerUseCrossOriginPolicy: true,
- Port: port
+ Urls: urls
);
(ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token);
diff --git a/src/mono/wasm/host/WebServer.cs b/src/mono/wasm/host/WebServer.cs
index 51bda60716740d..aed1948470334c 100644
--- a/src/mono/wasm/host/WebServer.cs
+++ b/src/mono/wasm/host/WebServer.cs
@@ -20,7 +20,7 @@ public class WebServer
{
internal static async Task<(ServerURLs, IWebHost)> StartAsync(WebServerOptions options, ILogger logger, CancellationToken token)
{
- string[]? urls = new string[] { $"http://127.0.0.1:{options.Port}", "https://127.0.0.1:0" };
+ string[] urls = options.Urls;
IWebHostBuilder builder = new WebHostBuilder()
.UseKestrel()
diff --git a/src/mono/wasm/host/WebServerOptions.cs b/src/mono/wasm/host/WebServerOptions.cs
index bc8b5f2acee63a..43e05c10b7c82e 100644
--- a/src/mono/wasm/host/WebServerOptions.cs
+++ b/src/mono/wasm/host/WebServerOptions.cs
@@ -15,6 +15,6 @@ internal sealed record WebServerOptions
string? ContentRootPath,
bool WebServerUseCors,
bool WebServerUseCrossOriginPolicy,
- int Port,
+ string [] Urls,
string DefaultFileName = "index.html"
);
diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs
index ae5e906c518a65..e09cdd61963fb2 100644
--- a/src/mono/wasm/host/WebServerStartup.cs
+++ b/src/mono/wasm/host/WebServerStartup.cs
@@ -1,13 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
using System.Net.WebSockets;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Web;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
+using Microsoft.WebAssembly.Diagnostics;
#nullable enable
@@ -16,9 +26,32 @@ namespace Microsoft.WebAssembly.AppHost;
internal sealed class WebServerStartup
{
private readonly IWebHostEnvironment _hostingEnvironment;
-
+ private static readonly object LaunchLock = new object();
+ private static string LaunchedDebugProxyUrl = "";
public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment;
+ public static int StartDebugProxy(string devToolsHost)
+ {
+ //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
+ //on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value
+ var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll");
+ var ownerPid = Environment.ProcessId;
+ var generateRandomPort = new Random().Next(5000, 5300);
+ var processStartInfo = new ProcessStartInfo
+ {
+ FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""),
+ Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}",
+ UseShellExecute = false,
+ RedirectStandardOutput = true,
+ };
+ var debugProxyProcess = Process.Start(processStartInfo);
+ if (debugProxyProcess is null)
+ {
+ throw new InvalidOperationException("Unable to start debug proxy process.");
+ }
+ return generateRandomPort;
+ }
+
public void Configure(IApplicationBuilder app, IOptions optionsContainer)
{
var provider = new FileExtensionContentTypeProvider();
@@ -73,9 +106,43 @@ public void Configure(IApplicationBuilder app, IOptions option
});
}
- // app.UseEndpoints(endpoints =>
- // {
- // endpoints.MapFallbackToFile(options.DefaultFileName);
- // });
+ app.Map("/debug", app =>
+ {
+ app.Run(async (context) =>
+ {
+ //debug from VS
+ var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!);
+ var browserParam = queryParams.Get("browser");
+ Uri? browserUrl = null;
+ var devToolsHost = "http://localhost:9222";
+ if (browserParam != null)
+ {
+ browserUrl = new Uri(browserParam);
+ devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}";
+ }
+ lock (LaunchLock)
+ {
+ if (LaunchedDebugProxyUrl == "")
+ {
+ LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}";
+ }
+ }
+ var requestPath = context.Request.Path.ToString();
+ if (requestPath == string.Empty)
+ {
+ requestPath = "/";
+ }
+ context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}");
+ await Task.FromResult(0);
+ });
+ });
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapGet("/", context =>
+ {
+ context.Response.Redirect("index.html", permanent: false);
+ return Task.CompletedTask;
+ });
+ });
}
}
diff --git a/src/mono/wasm/templates/templates/browser/.template.config/template.json b/src/mono/wasm/templates/templates/browser/.template.config/template.json
index d7d22668accdde..b2ae216e23db65 100644
--- a/src/mono/wasm/templates/templates/browser/.template.config/template.json
+++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json
@@ -16,6 +16,24 @@
"type": "project"
},
"symbols": {
+ "kestrelHttpPortGenerated": {
+ "type": "generated",
+ "generator": "port",
+ "parameters": {
+ "low": 5000,
+ "high": 5300
+ },
+ "replaces": "5000"
+ },
+ "kestrelHttpsPortGenerated": {
+ "type": "generated",
+ "generator": "port",
+ "parameters": {
+ "low": 7000,
+ "high": 7300
+ },
+ "replaces": "5001"
+ },
"framework": {
"type": "parameter",
"description": "The target framework for the project.",
diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json
new file mode 100644
index 00000000000000..0e5b784b708843
--- /dev/null
+++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "profiles": {
+ "browser.0": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}"
+ }
+ }
+}