Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions dotnet-blazor-wasm-debug-control.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this wasn't intended to be committed, but if it was, perhaps as a readme.md in the blazorDebug folder might be better?

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Blazor WebAssembly Browser Debug Control

## Summary

Expose the project path directly on the existing `blazorwasm` debug configuration so callers can launch browser and WebAssembly debugging for an already-running app without composing low-level debug adapter configurations. Also allow `blazorwasm` launch configurations to start only the app process by disabling browser launch.

The motivating integration is the Aspire browser launch command described in [Aspire Extension: Browser and WASM debug session support](https://github.com/microsoft/aspire/issues/17797). Aspire can receive a browser launch request for an already-running app and needs VS Code to start browser JavaScript debugging plus Blazor WebAssembly managed debugging. The Aspire extension should be able to invoke the existing high-level `blazorwasm` debug type instead of composing js-debug, `monovsdbg_wasm`, VSDbg bridge ports, and cascade termination behavior itself.

## Current Behavior

The existing `BlazorDebugConfigurationProvider` owns the detailed Blazor orchestration:

- launch the app for `request: "launch"`;
- read `launchSettings.json` for `inspectUri` and `applicationUrl`;
- launch Edge or Chrome through js-debug;
- optionally start the VSDbg WebAssembly bridge for supported .NET 9+ projects;
- track sibling sessions so terminating one Blazor session terminates the rest.

Today `launchBrowser` calls `useVSDbg` with a value derived from `cwd`. That is incorrect for callers such as Aspire, where the browser launch command can provide the `.csproj` path independently from the web root or working directory. Target framework detection should use a project path, not the launch working directory.

## Proposed Schema

Add `projectPath` to `blazorwasm` attach configurations:

```jsonc
{
"name": "C#: Launch Blazor Browser Only",
"type": "blazorwasm",
"request": "attach",
"projectPath": "${workspaceFolder}/Client/Client.csproj",
"url": "https://localhost:5001",
"browser": "edge"
}
```

The existing request semantics already cover the key modes:

- `request: "launch"`: start the app and browser/WASM debugging by default.
- `request: "attach"`: start browser/WASM debugging only against an already-running app.

For app-only scenarios, callers can use `request: "launch"` with browser launch disabled:

```jsonc
{
"name": "C#: Launch both app and browser",
"type": "blazorwasm",
"request": "launch",
"projectPath": "${workspaceFolder}/Client/Client.csproj",
"browser": "edge",
"launchBrowser": true
}

{
"name": "C#: Launch Blazor App Only",
"type": "blazorwasm",
"request": "launch",
"projectPath": "${workspaceFolder}/Client/Client.csproj",
"url": "https://localhost:5001",
"launchBrowser": false
}

{
"name": "C#: Attach debugging browser",
"type": "blazorwasm",
"request": "attach",
"projectPath": "${workspaceFolder}/Client/Client.csproj",
"url": "https://localhost:5001",
"browser": "edge"
}
```

## Aspire Mapping

Aspire browser launch data should map into the existing `blazorwasm` surface:

- DCP `url` maps to `url`.
- DCP project path maps to `projectPath` when it identifies a `.csproj`.
- DCP `web_root` maps to `webRoot` when it identifies a static web root.
- Browser selection maps to `browser`.
- Browser isolation flags should remain owned by the browser launcher path and should not require Aspire to know C# adapter internals.

## Implementation Notes

`BlazorDebugConfigurationProvider.launchBrowser` should resolve `configuration.projectPath` and pass that value to `useVSDbg`. It may fall back to `cwd` for compatibility with existing launch configurations, but `cwd` should no longer be the primary input for target framework detection.

`useVSDbg` already accepts either a `.csproj` file path or a directory, because `findCsprojFiles` handles both forms. Passing the actual project path fixes .NET 9+ detection for integrations where `cwd` is not the project file or project directory.

`BlazorDebugConfigurationProvider.resolveDebugConfiguration` should skip `launchBrowser` when `configuration.launchBrowser === false`. This keeps the existing launch path but allows callers to use `blazorwasm` for app-only launch when they intentionally do not want browser or WASM debug sessions.
25 changes: 25 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4646,6 +4646,10 @@
"description": "The directory of the Blazor WebAssembly app, defaults to the workspace folder.",
"default": "${workspaceFolder}"
},
"projectPath": {
"type": "string",
"description": "The path to the Blazor WebAssembly .csproj file. Used to determine target framework and managed WebAssembly debugging support."
},
"url": {
"type": "string",
"description": "The URL of the application",
Expand Down Expand Up @@ -4687,6 +4691,11 @@
"default": 30000,
"description": "Retry for this number of milliseconds to connect to browser."
},
"launchBrowser": {
"type": "boolean",
"description": "Controls whether the browser and WebAssembly debug sessions are launched. If false, only the Blazor WebAssembly app process is launched.",
"default": true
},
"program": {
"type": "string",
"default": "${workspaceFolder}/Server/bin/Debug/<target-framework>/<target-dll>",
Expand Down Expand Up @@ -4787,6 +4796,10 @@
},
"attach": {
"properties": {
"projectPath": {
"type": "string",
"description": "The path to the Blazor WebAssembly .csproj file. Used to determine target framework and managed WebAssembly debugging support."
},
"url": {
"type": "string",
"description": "The URL of the application",
Expand Down Expand Up @@ -4827,6 +4840,18 @@
"type": "number",
"default": 30000,
"description": "Retry for this number of milliseconds to connect to browser."
},
"browserConfig": {
"description": "Options passed to the underlying browser debugger.",
"type": "object",
"required": [],
"default": {}
},
"dotNetConfig": {
"description": "Options passed to the underlying .NET debugger. For more info, see https://github.com/dotnet/vscode-csharp/blob/main/debugger.md.",
"type": "object",
"required": [],
"default": {}
}
}
}
Expand Down
41 changes: 34 additions & 7 deletions src/razor/src/blazorDebug/blazorDebugConfigurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
this.logger.error('[DEBUGGER] Error while getting information from launchSettings.json: ', error as Error);
}

await this.launchBrowser(folder, configuration, inspectUri, url);
if (BlazorDebugConfigurationProvider.shouldLaunchBrowser(configuration)) {
await this.launchBrowser(folder, configuration, inspectUri, url);
}

/**
* If `resolveDebugConfiguration` returns undefined, then the debugger
Expand Down Expand Up @@ -222,19 +224,19 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
}
}

private static shouldLaunchBrowser(configuration: vscode.DebugConfiguration): boolean {
return configuration.launchBrowser !== false;
}

private async launchBrowser(
folder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
inspectUri: string,
url: string
) {
const originalInspectUri = inspectUri;
let folderPath = configuration.cwd;
if (folder && folderPath) {
folderPath = folderPath.replace('${workspaceFolder}', fileURLToPath(folder.uri.toString()));
folderPath = folderPath.replaceAll(/[\\/]/g, path.sep);
}
const useVSDbg = await BlazorDebugConfigurationProvider.useVSDbg(folderPath ? folderPath : '');
const projectPath = BlazorDebugConfigurationProvider.resolveProjectPath(folder, configuration);
const useVSDbg = await BlazorDebugConfigurationProvider.useVSDbg(projectPath);
let portBrowserDebug = -1;
if (useVSDbg) {
[inspectUri, portBrowserDebug] = await this.attachToAppOnBrowser(folder, configuration, url);
Expand Down Expand Up @@ -317,6 +319,31 @@ export class BlazorDebugConfigurationProvider implements vscode.DebugConfigurati
}
}

private static resolveProjectPath(
folder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration
): string {
const configuredProjectPath = configuration.projectPath as string | undefined;
if (configuredProjectPath) {
return BlazorDebugConfigurationProvider.resolveWorkspaceFolderToken(folder, configuredProjectPath);
}

const cwd = configuration.cwd as string | undefined;
if (cwd) {
return BlazorDebugConfigurationProvider.resolveWorkspaceFolderToken(folder, cwd);
}

return '';
}

private static resolveWorkspaceFolderToken(folder: vscode.WorkspaceFolder | undefined, value: string): string {
if (folder) {
value = value.replace('${workspaceFolder}', fileURLToPath(folder.uri.toString()));
}

return value.replaceAll(/[\\/]/g, path.sep);
}

private async attachToAppOnBrowser(
folder: vscode.WorkspaceFolder | undefined,
configuration: vscode.DebugConfiguration,
Expand Down
Loading