From 130f0cfe39b9f546a45751988fbeda8f2b3b58d2 Mon Sep 17 00:00:00 2001 From: Wes Sleeman Date: Sun, 14 Sep 2025 15:49:20 -0700 Subject: [PATCH] Dotnet watch browser refresh configurable port (#50629) --- documentation/manpages/sdk/dotnet-watch.1 | 2 +- .../HotReloadClient/Web/BrowserRefreshServer.cs | 8 +++++--- .../dotnet-watch/CommandLine/EnvironmentOptions.cs | 2 ++ .../dotnet-watch/CommandLine/EnvironmentVariables.cs | 6 +++++- .../HotReload/AppModels/WebApplicationAppModel.cs | 1 + 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/documentation/manpages/sdk/dotnet-watch.1 b/documentation/manpages/sdk/dotnet-watch.1 index 26ab19a2e9ec..6a02e55593a9 100644 --- a/documentation/manpages/sdk/dotnet-watch.1 +++ b/documentation/manpages/sdk/dotnet-watch.1 @@ -177,7 +177,7 @@ The class uses \f \f[B]\f[VB]DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME\f[B]\f[R] .RS 2 .PP -As part of \f[V]dotnet watch\f[R], the browser refresh server mechanism reads this value to determine the WebSocket host environment. +As part of \f[V]dotnet watch\f[R], the browser refresh server mechanism reads this value to determine the WebSocket host environment's hostname. The value \f[V]127.0.0.1\f[R] is replaced by \f[V]localhost\f[R], and the \f[V]http://\f[R] and \f[V]https://\f[R] schemes are replaced with \f[V]ws://\f[R] and \f[V]wss://\f[R] respectively. .RE .IP \[bu] 2 diff --git a/src/BuiltInTools/HotReloadClient/Web/BrowserRefreshServer.cs b/src/BuiltInTools/HotReloadClient/Web/BrowserRefreshServer.cs index 0030d669c21c..241bcbe0d134 100644 --- a/src/BuiltInTools/HotReloadClient/Web/BrowserRefreshServer.cs +++ b/src/BuiltInTools/HotReloadClient/Web/BrowserRefreshServer.cs @@ -27,12 +27,13 @@ namespace Microsoft.DotNet.HotReload; /// /// Kestrel-based Browser Refesh Server implementation. /// -internal sealed class BrowserRefreshServer( +internal sealed class BrowserRefreshServer( ILogger logger, ILoggerFactory loggerFactory, string middlewareAssemblyPath, string dotnetPath, string? autoReloadWebSocketHostName, + int? autoReloadWebSocketPort, bool suppressTimeouts) : AbstractBrowserRefreshServer(middlewareAssemblyPath, logger, loggerFactory) { @@ -44,6 +45,7 @@ protected override bool SuppressTimeouts protected override async ValueTask CreateAndStartHostAsync(CancellationToken cancellationToken) { var hostName = autoReloadWebSocketHostName ?? "127.0.0.1"; + var port = autoReloadWebSocketPort ?? 0; var supportsTls = await IsTlsSupportedAsync(cancellationToken); @@ -53,11 +55,11 @@ protected override async ValueTask CreateAndStartHostAsync(Cancel builder.UseKestrel(); if (supportsTls) { - builder.UseUrls($"https://{hostName}:0", $"http://{hostName}:0"); + builder.UseUrls($"https://{hostName}:{port}", $"http://{hostName}:{port}"); } else { - builder.UseUrls($"http://{hostName}:0"); + builder.UseUrls($"http://{hostName}:{port}"); } builder.Configure(app => diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs index 01ea83ed6df5..f2fbfa7cabd3 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentOptions.cs @@ -36,6 +36,7 @@ internal sealed record EnvironmentOptions( bool SuppressEmojis = false, bool RestartOnRudeEdit = false, string? AutoReloadWebSocketHostName = null, + int? AutoReloadWebSocketPort = null, string? BrowserPath = null, TestFlags TestFlags = TestFlags.None, string TestOutput = "") @@ -53,6 +54,7 @@ internal sealed record EnvironmentOptions( SuppressEmojis: EnvironmentVariables.SuppressEmojis, RestartOnRudeEdit: EnvironmentVariables.RestartOnRudeEdit, AutoReloadWebSocketHostName: EnvironmentVariables.AutoReloadWSHostName, + AutoReloadWebSocketPort: EnvironmentVariables.AutoReloadWSPort, BrowserPath: EnvironmentVariables.BrowserPath, TestFlags: EnvironmentVariables.TestFlags, TestOutput: EnvironmentVariables.TestOutputDir diff --git a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs index c222eb9a3f73..28763d7f3223 100644 --- a/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs +++ b/src/BuiltInTools/dotnet-watch/CommandLine/EnvironmentVariables.cs @@ -39,9 +39,10 @@ public static class Names public static bool SuppressBrowserRefresh => ReadBool(Names.SuppressBrowserRefresh); public static TestFlags TestFlags => Environment.GetEnvironmentVariable("__DOTNET_WATCH_TEST_FLAGS") is { } value ? Enum.Parse(value) : TestFlags.None; - public static string TestOutputDir => Environment.GetEnvironmentVariable("__DOTNET_WATCH_TEST_OUTPUT_DIR") ?? ""; + public static string TestOutputDir => Environment.GetEnvironmentVariable("__DOTNET_WATCH_TEST_OUTPUT_DIR") ?? ""; public static string? AutoReloadWSHostName => Environment.GetEnvironmentVariable("DOTNET_WATCH_AUTO_RELOAD_WS_HOSTNAME"); + public static int? AutoReloadWSPort => ReadInt("DOTNET_WATCH_AUTO_RELOAD_WS_PORT"); public static string? BrowserPath => Environment.GetEnvironmentVariable("DOTNET_WATCH_BROWSER_PATH"); private static bool ReadBool(string variableName) @@ -49,4 +50,7 @@ private static bool ReadBool(string variableName) private static TimeSpan? ReadTimeSpan(string variableName) => Environment.GetEnvironmentVariable(variableName) is var value && long.TryParse(value, out var intValue) && intValue >= 0 ? TimeSpan.FromMilliseconds(intValue) : null; + + private static int? ReadInt(string variableName) + => Environment.GetEnvironmentVariable(variableName) is var value && int.TryParse(value, out var intValue) ? intValue : null; } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs index 65424aef7ec3..4ca515a862a6 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/AppModels/WebApplicationAppModel.cs @@ -60,6 +60,7 @@ private static string GetMiddlewareAssemblyPath() middlewareAssemblyPath: GetMiddlewareAssemblyPath(), dotnetPath: context.EnvironmentOptions.MuxerPath, autoReloadWebSocketHostName: context.EnvironmentOptions.AutoReloadWebSocketHostName, + autoReloadWebSocketPort: context.EnvironmentOptions.AutoReloadWebSocketPort, suppressTimeouts: context.EnvironmentOptions.TestFlags != TestFlags.None); }