From 85752afec9ecf5b06b75a61171a577173992bf32 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:14:04 +0000 Subject: [PATCH 1/2] Initial plan From ca543a959050080bab62d6f418fa75d55dc9e17f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 10:29:59 +0000 Subject: [PATCH 2/2] Remove DebugProxy from dev server package - complete implementation Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../DevServer/src/Server/Startup.cs | 2 - .../Server/Startup.cs | 1 - .../Server/src/DebugProxyLauncher.cs | 233 ------------------ ...tCore.Components.WebAssembly.Server.csproj | 10 - .../Server/src/PublicAPI.Shipped.txt | 3 - ...semblyNetDebugProxyAppBuilderExtensions.cs | 69 ------ .../HostedInAspNet.Server/Startup.cs | 1 - .../Wasm.Prerendered.Server/Startup.cs | 1 - .../BlazorWebCSharp.1/Program.Main.cs | 7 +- .../BlazorWebCSharp.1/Program.cs | 7 +- 10 files changed, 2 insertions(+), 332 deletions(-) delete mode 100644 src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs delete mode 100644 src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs diff --git a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs index 4be74f8b19d6..0a4f354ebbb1 100644 --- a/src/Components/WebAssembly/DevServer/src/Server/Startup.cs +++ b/src/Components/WebAssembly/DevServer/src/Server/Startup.cs @@ -29,8 +29,6 @@ public static void Configure(IApplicationBuilder app, IConfiguration configurati app.UseDeveloperExceptionPage(); EnableConfiguredPathbase(app, configuration); - app.UseWebAssemblyDebugging(); - var webHostEnvironment = app.ApplicationServices.GetRequiredService(); var applyCopHeaders = configuration.GetValue("ApplyCopHeaders"); diff --git a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Startup.cs b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Startup.cs index 10e942763938..cdbf48b2306a 100644 --- a/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Startup.cs +++ b/src/Components/WebAssembly/Samples/HostedBlazorWebassemblyApp/Server/Startup.cs @@ -39,7 +39,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseWebAssemblyDebugging(); } else { diff --git a/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs b/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs deleted file mode 100644 index a7ba12377252..000000000000 --- a/src/Components/WebAssembly/Server/src/DebugProxyLauncher.cs +++ /dev/null @@ -1,233 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.CommandLineUtils; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.AspNetCore.Builder; - -internal static class DebugProxyLauncher -{ - private static readonly object LaunchLock = new object(); - private static readonly TimeSpan DebugProxyLaunchTimeout = TimeSpan.FromSeconds(10); - private static Task? LaunchedDebugProxyUrl; - private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?.*)$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly Regex ApplicationStartedRegex = new Regex(@"^\s*Application started\. Press Ctrl\+C to shut down\.$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly Regex NowListeningFirefoxRegex = new Regex(@"^\s*Debug proxy for firefox now listening on tcp://(?.*)\. And expecting firefox at port 6000\.$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly string[] MessageSuppressionPrefixes = new[] - { - "Hosting environment:", - "Content root path:", - "Now listening on:", - "Application started. Press Ctrl+C to shut down.", - "Debug proxy for firefox now", - }; - - public static Task EnsureLaunchedAndGetUrl(IServiceProvider serviceProvider, string devToolsHost, bool isFirefox) - { - lock (LaunchLock) - { - LaunchedDebugProxyUrl ??= LaunchAndGetUrl(serviceProvider, devToolsHost, isFirefox); - - return LaunchedDebugProxyUrl; - } - } - - private static string GetIgnoreProxyForLocalAddress() - { - var noProxyEnvVar = Environment.GetEnvironmentVariable("NO_PROXY"); - if (noProxyEnvVar is not null) - { - var noProxyEnvVarValues = noProxyEnvVar.Split(",", StringSplitOptions.TrimEntries); - if (noProxyEnvVarValues.Any(noProxyValue => noProxyValue.Equals("localhost") || noProxyValue.Equals("127.0.0.1"))) - { - return "--IgnoreProxyForLocalAddress True"; - } - Console.WriteLine($"Invalid value for NO_PROXY: {noProxyEnvVar} (Expected values: \"localhost\" or \"127.0.0.1\")"); - } - return ""; - } - - private static async Task LaunchAndGetUrl(IServiceProvider serviceProvider, string devToolsHost, bool isFirefox) - { - var tcs = new TaskCompletionSource(); - - var environment = serviceProvider.GetRequiredService(); - var executablePath = LocateDebugProxyExecutable(environment); - var muxerPath = DotNetMuxer.MuxerPathOrDefault(); - var ownerPid = Environment.ProcessId; - var ignoreProxyForLocalAddress = GetIgnoreProxyForLocalAddress(); - var processStartInfo = new ProcessStartInfo - { - FileName = muxerPath, - Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --IsFirefoxDebugging {isFirefox} --FirefoxProxyPort 6001 {ignoreProxyForLocalAddress}", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - RemoveUnwantedEnvironmentVariables(processStartInfo.Environment); - - using var cts = new CancellationTokenSource(DebugProxyLaunchTimeout); - var ctr = default(CancellationTokenRegistration); - var debugProxyProcess = Process.Start(processStartInfo); - if (debugProxyProcess is null) - { - tcs.TrySetException(new InvalidOperationException("Unable to start debug proxy process.")); - } - else - { - PassThroughConsoleOutput(debugProxyProcess); - CompleteTaskWhenServerIsReady(debugProxyProcess, isFirefox, tcs); - - ctr = cts.Token.Register(() => - { - tcs.TrySetException(new TimeoutException($"Failed to start the debug proxy within the timeout period of {DebugProxyLaunchTimeout.TotalSeconds} seconds.")); - }); - } - - try - { - return await tcs.Task; - } - finally - { - ctr.Dispose(); - } - } - - private static void RemoveUnwantedEnvironmentVariables(IDictionary environment) - { - // Generally we expect to pass through most environment variables, since dotnet might - // need them for arbitrary reasons to function correctly. However, we specifically don't - // want to pass through any ASP.NET Core hosting related ones, since the child process - // shouldn't be trying to use the same port numbers, etc. In particular we need to break - // the association with IISExpress and the MS-ASPNETCORE-TOKEN check. - // For more context on this, see https://github.com/dotnet/aspnetcore/issues/20308. - var keysToRemove = environment.Keys.Where(key => key.StartsWith("ASPNETCORE_", StringComparison.Ordinal)).ToList(); - foreach (var key in keysToRemove) - { - environment.Remove(key); - } - } - - private static string LocateDebugProxyExecutable(IWebHostEnvironment environment) - { - if (string.IsNullOrEmpty(environment.ApplicationName)) - { - throw new InvalidOperationException("IWebHostEnvironment.ApplicationName is required to be set in order to start the debug proxy."); - } - var assembly = Assembly.Load(environment.ApplicationName); - var debugProxyPath = Path.Combine( - Path.GetDirectoryName(assembly.Location)!, - "BlazorDebugProxy", - "BrowserDebugHost.dll"); - - if (!File.Exists(debugProxyPath)) - { - throw new FileNotFoundException( - $"Cannot start debug proxy because it cannot be found at '{debugProxyPath}'"); - } - - return debugProxyPath; - } - - private static void PassThroughConsoleOutput(Process process) - { - process.OutputDataReceived += (sender, eventArgs) => - { - // It's confusing if the debug proxy emits its own startup status messages, because the developer - // may think the ports/environment/paths refer to their actual application. So we want to suppress - // them, but we can't stop the debug proxy app from emitting the messages entirely (e.g., via - // SuppressStatusMessages) because we need the "Now listening on" one to detect the chosen port. - // Instead, we'll filter out known strings from the passthrough logic. It's legit to hardcode these - // strings because they are also hardcoded like this inside WebHostExtensions.cs and can't vary - // according to culture. - if (eventArgs.Data is not null) - { - foreach (var prefix in MessageSuppressionPrefixes) - { - if (eventArgs.Data.StartsWith(prefix, StringComparison.Ordinal)) - { - return; - } - } - } - - Console.WriteLine(eventArgs.Data); - }; - } - - private static void CompleteTaskWhenServerIsReady(Process aspNetProcess, bool isFirefox, TaskCompletionSource taskCompletionSource) - { - string? capturedUrl = null; - var errorEncountered = false; - - aspNetProcess.ErrorDataReceived += OnErrorDataReceived; - aspNetProcess.BeginErrorReadLine(); - - aspNetProcess.OutputDataReceived += OnOutputDataReceived; - aspNetProcess.BeginOutputReadLine(); - - void OnErrorDataReceived(object sender, DataReceivedEventArgs eventArgs) - { - if (!string.IsNullOrEmpty(eventArgs.Data)) - { - taskCompletionSource.TrySetException(new InvalidOperationException( - eventArgs.Data)); - errorEncountered = true; - } - } - - void OnOutputDataReceived(object sender, DataReceivedEventArgs eventArgs) - { - if (string.IsNullOrEmpty(eventArgs.Data)) - { - if (!errorEncountered) - { - taskCompletionSource.TrySetException(new InvalidOperationException( - "Expected output has not been received from the application.")); - } - return; - } - - if (ApplicationStartedRegex.IsMatch(eventArgs.Data) && !isFirefox) - { - aspNetProcess.OutputDataReceived -= OnOutputDataReceived; - aspNetProcess.ErrorDataReceived -= OnErrorDataReceived; - if (!string.IsNullOrEmpty(capturedUrl)) - { - taskCompletionSource.TrySetResult(capturedUrl); - } - else - { - taskCompletionSource.TrySetException(new InvalidOperationException( - "The application started listening without first advertising a URL")); - } - } - else - { - var matchFirefox = NowListeningFirefoxRegex.Match(eventArgs.Data); - if (matchFirefox.Success && isFirefox) - { - aspNetProcess.OutputDataReceived -= OnOutputDataReceived; - aspNetProcess.ErrorDataReceived -= OnErrorDataReceived; - capturedUrl = matchFirefox.Groups["url"].Value; - taskCompletionSource.TrySetResult(capturedUrl); - return; - } - var match = NowListeningRegex.Match(eventArgs.Data); - if (match.Success) - { - capturedUrl = match.Groups["url"].Value; - capturedUrl = capturedUrl.Replace("http://", "ws://"); - capturedUrl = capturedUrl.Replace("https://", "wss://"); - } - } - } - } -} diff --git a/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj b/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj index b3f42e28fe8a..01275bb768ef 100644 --- a/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj +++ b/src/Components/WebAssembly/Server/src/Microsoft.AspNetCore.Components.WebAssembly.Server.csproj @@ -16,7 +16,6 @@ - @@ -26,17 +25,8 @@ - - - - - - - - - diff --git a/src/Components/WebAssembly/Server/src/PublicAPI.Shipped.txt b/src/Components/WebAssembly/Server/src/PublicAPI.Shipped.txt index 63591709c1d6..3aee74e630a3 100644 --- a/src/Components/WebAssembly/Server/src/PublicAPI.Shipped.txt +++ b/src/Components/WebAssembly/Server/src/PublicAPI.Shipped.txt @@ -3,9 +3,7 @@ ~Microsoft.AspNetCore.Components.WebAssembly.Server.TargetPickerUi.TargetPickerUi(string debugProxyUrl, string devToolsHost) -> void ~static Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions.UseBlazorFrameworkFiles(this Microsoft.AspNetCore.Builder.IApplicationBuilder applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder ~static Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions.UseBlazorFrameworkFiles(this Microsoft.AspNetCore.Builder.IApplicationBuilder builder, Microsoft.AspNetCore.Http.PathString pathPrefix) -> Microsoft.AspNetCore.Builder.IApplicationBuilder -~static Microsoft.AspNetCore.Builder.WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging(this Microsoft.AspNetCore.Builder.IApplicationBuilder app) -> void Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions -Microsoft.AspNetCore.Builder.WebAssemblyNetDebugProxyAppBuilderExtensions Microsoft.AspNetCore.Builder.WebAssemblyRazorComponentsEndpointConventionBuilderExtensions Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions Microsoft.AspNetCore.Components.WebAssembly.Server.AuthenticationStateSerializationOptions.AuthenticationStateSerializationOptions() -> void @@ -26,7 +24,6 @@ Microsoft.AspNetCore.Components.WebAssembly.Server.WebAssemblyComponentsEndpoint Microsoft.Extensions.DependencyInjection.WebAssemblyRazorComponentsBuilderExtensions static Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions.UseBlazorFrameworkFiles(this Microsoft.AspNetCore.Builder.IApplicationBuilder! applicationBuilder) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! static Microsoft.AspNetCore.Builder.ComponentsWebAssemblyApplicationBuilderExtensions.UseBlazorFrameworkFiles(this Microsoft.AspNetCore.Builder.IApplicationBuilder! builder, Microsoft.AspNetCore.Http.PathString pathPrefix) -> Microsoft.AspNetCore.Builder.IApplicationBuilder! -static Microsoft.AspNetCore.Builder.WebAssemblyNetDebugProxyAppBuilderExtensions.UseWebAssemblyDebugging(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app) -> void static Microsoft.AspNetCore.Builder.WebAssemblyRazorComponentsEndpointConventionBuilderExtensions.AddInteractiveWebAssemblyRenderMode(this Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! builder, System.Action? callback = null) -> Microsoft.AspNetCore.Builder.RazorComponentsEndpointConventionBuilder! static Microsoft.Extensions.DependencyInjection.WebAssemblyRazorComponentsBuilderExtensions.AddAuthenticationStateSerialization(this Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! static Microsoft.Extensions.DependencyInjection.WebAssemblyRazorComponentsBuilderExtensions.AddInteractiveWebAssemblyComponents(this Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! builder) -> Microsoft.Extensions.DependencyInjection.IRazorComponentsBuilder! diff --git a/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs b/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs deleted file mode 100644 index 59b81f59eee8..000000000000 --- a/src/Components/WebAssembly/Server/src/WebAssemblyNetDebugProxyAppBuilderExtensions.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Net; -using System.Web; -using Microsoft.AspNetCore.Components.WebAssembly.Server; - -namespace Microsoft.AspNetCore.Builder; - -/// -/// Provides infrastructure for debugging Blazor WebAssembly applications. -/// -public static class WebAssemblyNetDebugProxyAppBuilderExtensions -{ - /// - /// Adds middleware needed for debugging Blazor WebAssembly applications - /// inside Chromium dev tools. - /// - public static void UseWebAssemblyDebugging(this IApplicationBuilder app) - { - app.Map("/_framework/debug", app => - { - app.Run(async (context) => - { - 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}"; - } - var isFirefox = string.IsNullOrEmpty(queryParams.Get("isFirefox")) ? false : true; - if (isFirefox) - { - devToolsHost = "localhost:6000"; - } - var debugProxyBaseUrl = await DebugProxyLauncher.EnsureLaunchedAndGetUrl(context.RequestServices, devToolsHost, isFirefox); - var requestPath = context.Request.Path.ToString(); - if (requestPath == string.Empty) - { - requestPath = "/"; - } - - switch (requestPath) - { - case "/": - var targetPickerUi = new TargetPickerUi(debugProxyBaseUrl, devToolsHost); - if (isFirefox) - { - await targetPickerUi.DisplayFirefox(context); - } - else - { - await targetPickerUi.Display(context); - } - break; - case "/ws-proxy": - context.Response.Redirect($"{debugProxyBaseUrl}{browserUrl!.PathAndQuery}"); - break; - default: - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - break; - } - }); - }); - } -} diff --git a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs index bf75aad5012d..82ea4d0823f0 100644 --- a/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs +++ b/src/Components/WebAssembly/testassets/HostedInAspNet.Server/Startup.cs @@ -41,7 +41,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env, BootReso if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseWebAssemblyDebugging(); } if (mapAllApps || mapAlternativePathApp) diff --git a/src/Components/WebAssembly/testassets/Wasm.Prerendered.Server/Startup.cs b/src/Components/WebAssembly/testassets/Wasm.Prerendered.Server/Startup.cs index 867077883abe..d3b126f7e031 100644 --- a/src/Components/WebAssembly/testassets/Wasm.Prerendered.Server/Startup.cs +++ b/src/Components/WebAssembly/testassets/Wasm.Prerendered.Server/Startup.cs @@ -22,7 +22,6 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); - app.UseWebAssemblyDebugging(); } app.UseHttpsRedirection(); diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.Main.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.Main.cs index 1b8771773c04..5310b49f23e0 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.Main.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.Main.cs @@ -85,15 +85,10 @@ public static void Main(string[] args) var app = builder.Build(); // Configure the HTTP request pipeline. -#if (UseWebAssembly || IndividualLocalAuth) +#if (IndividualLocalAuth) if (app.Environment.IsDevelopment()) { -#if (UseWebAssembly) - app.UseWebAssemblyDebugging(); -#endif -#if (IndividualLocalAuth) app.UseMigrationsEndPoint(); -#endif } else #else diff --git a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.cs b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.cs index ae3fb9dcfce7..4c345055813b 100644 --- a/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.cs +++ b/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1/Program.cs @@ -79,15 +79,10 @@ var app = builder.Build(); // Configure the HTTP request pipeline. -#if (UseWebAssembly || IndividualLocalAuth) +#if (IndividualLocalAuth) if (app.Environment.IsDevelopment()) { -#if (UseWebAssembly) - app.UseWebAssemblyDebugging(); -#endif -#if (IndividualLocalAuth) app.UseMigrationsEndPoint(); -#endif } else #else