From 115b8f4c0a72668c3a22f99914561ee917564c4a Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 21 Sep 2022 15:06:27 -0300 Subject: [PATCH 1/9] Support create, debugging and running wasmbrowser template from VS --- src/mono/wasm/build/WasmApp.targets | 5 ++ src/mono/wasm/host/BrowserHost.cs | 10 ++- src/mono/wasm/host/WebServer.cs | 2 +- src/mono/wasm/host/WebServerOptions.cs | 2 +- src/mono/wasm/host/WebServerStartup.cs | 77 +++++++++++++++++-- .../browser/.template.config/template.json | 23 +++++- .../browser/Properties/launchSettings.json | 13 ++++ 7 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 src/mono/wasm/templates/templates/browser/Properties/launchSettings.json 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..80977ee9d94ec0 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")); + var urls = new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" }; + if (envVars["ASPNETCORE_URLS"] is not null) + urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + (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 + Url: 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 8051e4c6aab777..30ff32ec141d1a 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -2,6 +2,7 @@ "$schema": "http://json.schemastore.org/template", "author": "Microsoft", "classifications": [ "Web", "WebAssembly", "Browser" ], + "generatorVersions": "[1.0.0.0-*)", "identity": "WebAssembly.Browser", "name": "WebAssembly Browser App", "shortName": "wasmbrowser", @@ -10,5 +11,25 @@ "tags": { "language": "C#", "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" + } } -} +} \ No newline at end of file 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..21c5de00ca5da3 --- /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}" + } + } + } \ No newline at end of file From 21c1b633d2b60c2ff7abe0e674c32818268b3797 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 21 Sep 2022 15:11:35 -0300 Subject: [PATCH 2/9] addings extra line in the end of the file --- .../templates/templates/browser/.template.config/template.json | 2 +- .../templates/templates/browser/Properties/launchSettings.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) 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 30ff32ec141d1a..5fa2bac372a72f 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -32,4 +32,4 @@ "replaces": "5001" } } -} \ No newline at end of file +} diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json index 21c5de00ca5da3..8b413096c9ec5f 100644 --- a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json +++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json @@ -10,4 +10,5 @@ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" } } - } \ No newline at end of file + } + \ No newline at end of file From 6b15f080b8740b4f6efea3c96f5e42f1696868e5 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 21 Sep 2022 15:12:35 -0300 Subject: [PATCH 3/9] remove extra spaces --- .../templates/templates/browser/Properties/launchSettings.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json index 8b413096c9ec5f..d5f38ff7428126 100644 --- a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json +++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json @@ -10,5 +10,4 @@ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" } } - } - \ No newline at end of file +} \ No newline at end of file From 75ac7ed3ed9ca674dba1f5f2a37bd5a6e06975c7 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 21 Sep 2022 15:13:36 -0300 Subject: [PATCH 4/9] fix compilation error --- src/mono/wasm/host/BrowserHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index 80977ee9d94ec0..73b10a4163d337 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -104,7 +104,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke ContentRootPath: Path.GetFullPath(appPath), WebServerUseCors: true, WebServerUseCrossOriginPolicy: true, - Url: urls + Urls: urls ); (ServerURLs serverURLs, IWebHost host) = await WebServer.StartAsync(options, _logger, token); From 47b99eae26cf7499c52a8a325e46e87e6d437960 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Wed, 21 Sep 2022 15:16:46 -0300 Subject: [PATCH 5/9] adding extra line in the end of the file --- .../templates/templates/browser/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json index d5f38ff7428126..0e5b784b708843 100644 --- a/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json +++ b/src/mono/wasm/templates/templates/browser/Properties/launchSettings.json @@ -10,4 +10,4 @@ "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" } } -} \ No newline at end of file +} From 32d693a3584724da2f0f131e66a1772bf426542e Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Fri, 23 Sep 2022 12:31:46 -0300 Subject: [PATCH 6/9] Addressing @lewing comment. --- src/mono/wasm/host/BrowserHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index 73b10a4163d337..f546127426cd2f 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -71,7 +71,7 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke debugging: _args.CommonConfig.Debugging); runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); - var urls = new string[] { $"http://127.0.0.1:{_args.CommonConfig.HostProperties.WebServerPort}", "https://127.0.0.1:0" }; + var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" }; if (envVars["ASPNETCORE_URLS"] is not null) urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); From 33246a727c4491bd50889da972ab8fe348f453fe Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 27 Sep 2022 19:50:52 -0400 Subject: [PATCH 7/9] Fix missing closing brace in wasmbrowser .. template.json . Fixes: ``` [] Error: Failed to load template from /datadisks/disk1/work/C9390AE6/w/AAE00933/e/dotnet-net7/template-packs/microsoft.net.runtime.webassembly.templates.7.0.0-ci.nupkg(/content/templates/browser/.template.config/template.json). [] Details: Newtonsoft.Json.JsonReaderException: After parsing a value an unexpected character was encountered: ". Path 'symbols.kestrelHttpsPortGenerated.replaces', line 36, position 4. [] at Newtonsoft.Json.JsonTextReader.ParsePostValue(Boolean ignoreComments) [] at Newtonsoft.Json.JsonTextReader.Read() [] at Newtonsoft.Json.Linq.JContainer.ReadContentFrom(JsonReader r, JsonLoadSettings settings) [] at Newtonsoft.Json.Linq.JContainer.ReadTokenFrom(JsonReader reader, JsonLoadSettings options) [] at Newtonsoft.Json.Linq.JObject.Load(JsonReader reader, JsonLoadSettings settings) [] at Microsoft.TemplateEngine.JExtensions.ReadJObjectFromIFile(IFile file) [] at Microsoft.TemplateEngine.Orchestrator.RunnableProjects.RunnableProjectConfig..ctor(IEngineEnvironmentSettings settings, IGenerator generator, IFile templateFile, IFile hostConfigFile, IFile localeConfigFile, String baselineName) [] at Microsoft.TemplateEngine.Orchestrator.RunnableProjects.RunnableProjectGenerator.Microsoft.TemplateEngine.Abstractions.IGenerator.GetTemplatesAndLangpacksFromDir(IMountPoint source, IList`1& localizations) ``` --- .../templates/browser/.template.config/template.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 9a55b2652779b1..b2ae216e23db65 100644 --- a/src/mono/wasm/templates/templates/browser/.template.config/template.json +++ b/src/mono/wasm/templates/templates/browser/.template.config/template.json @@ -23,7 +23,7 @@ "low": 5000, "high": 5300 }, - "replaces": "5000" + "replaces": "5000" }, "kestrelHttpsPortGenerated": { "type": "generated", @@ -32,7 +32,8 @@ "low": 7000, "high": 7300 }, - "replaces": "5001" + "replaces": "5001" + }, "framework": { "type": "parameter", "description": "The target framework for the project.", From 1bda7af99e4384b5bacb18f1ace538a8cd70a64e Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 27 Sep 2022 08:26:04 +0000 Subject: [PATCH 8/9] WasmAppHost: use dictionary.TryGetValue instead of directly indexing --- src/mono/wasm/host/BrowserHost.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index f546127426cd2f..1ed41debd4fdb9 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -72,8 +72,8 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" }; - if (envVars["ASPNETCORE_URLS"] is not null) - urls = envVars["ASPNETCORE_URLS"].Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + if (envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls)) + urls = aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); (ServerURLs serverURLs, IWebHost host) = await StartWebServerAsync(_args.CommonConfig.AppPath, _args.ForwardConsoleOutput ?? false, From 5d143c9b6c0069593bdb8234e6971e2c625d54e9 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Tue, 27 Sep 2022 08:30:49 -0400 Subject: [PATCH 9/9] WasmAppHost: Use 127.0.0.1 for dynamic port binding ``` WasmAppHost --runtime-config /datadisks/disk1/work/99FC08A1/w/B6B509D6/e/browser_Debug_crvdkcbp.f3f/bin/Debug/net7.0/browser-wasm/AppBundle/browser_Debug_crvdkcbp.f3f.runtimeconfig.json --forward-console Unhandled exception. System.InvalidOperationException: Dynamic port binding is not supported when binding to localhost. You must either bind to 127.0.0.1:0 or [::1]:0, or both. at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions..ctor(Int32 port) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.ParseAddress(String address, Boolean& https) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(IEnumerable`1 listenOptions, AddressBindContext context, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) at Microsoft.AspNetCore.Hosting.WebHost.StartAsync(CancellationToken cancellationToken) at Microsoft.WebAssembly.AppHost.WebServer.StartAsync(WebServerOptions options, ILogger logger, CancellationToken token) at Microsoft.WebAssembly.AppHost.BrowserHost.StartWebServerAsync(String appPath, Boolean forwardConsole, String[] urls, CancellationToken token) at Microsoft.WebAssembly.AppHost.BrowserHost.RunAsync(ILoggerFactory loggerFactory, CancellationToken token) at Microsoft.WebAssembly.AppHost.BrowserHost.InvokeAsync(CommonConfiguration commonArgs, ILoggerFactory loggerFactory, ILogger logger, CancellationToken token) at Microsoft.WebAssembly.AppHost.WasmAppHost.Main(String[] args) at Microsoft.WebAssembly.AppHost.WasmAppHost.
(String[] args) ``` --- src/mono/wasm/host/BrowserHost.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index 1ed41debd4fdb9..a592ca7386fff3 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -71,9 +71,9 @@ private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken toke debugging: _args.CommonConfig.Debugging); runArgsJson.Save(Path.Combine(_args.CommonConfig.AppPath, "runArgs.json")); - var urls = new string[] { $"http://localhost:{_args.CommonConfig.HostProperties.WebServerPort}", "https://localhost:0" }; - if (envVars.TryGetValue("ASPNETCORE_URLS", out string? aspnetUrls)) - urls = aspnetUrls.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + 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,