close
Skip to content

Add new webworker template project#65037

Merged
ilonatommy merged 50 commits into
dotnet:mainfrom
ilonatommy:webworker-template-as-item
Feb 6, 2026
Merged

Add new webworker template project#65037
ilonatommy merged 50 commits into
dotnet:mainfrom
ilonatommy:webworker-template-as-item

Conversation

@ilonatommy
Copy link
Copy Markdown
Member

@ilonatommy ilonatommy commented Jan 13, 2026

Worker Class Library template ( dotnet new webworker)

Summary

Adds a new .NET Web Worker project template that provides infrastructure for running .NET code in a WebWorker, keeping your WebAssembly UI responsive during heavy computations.

Background

Blazor WASM applications with heavy computing had to rely on server requests. Computing on the UI thread interfered with UI rendering and affected UX. In .NET 10 we added an article with a sample application to make offloading heavy work to a WebWorker easier. In .NET 11 we decided to add official template support. This PR adds a project template that enables running .NET code in a WebWorker.

Template Output

MyWebWorker/
├── MyWebWorker.csproj
├── WorkerClient.cs              # C# client with factory pattern
└── wwwroot/
    ├── worker-client.js         # JS WorkerClient class
    └── dotnet-web-worker.js     # Worker entry point

Usage

# 1. Create Blazor WebAssembly app
dotnet new blazorwasm -n MyApp

# 2. Create .NET Web Worker
dotnet new webworker -n MyWebWorker

# 3. Add reference
cd MyApp
dotnet add reference ../MyWebWorker/MyWebWorker.csproj

# 4. Add AllowUnsafeBlocks to app (required for [JSExport])
# <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

# 5. Create worker methods in the app
// In MyApp - worker methods with [JSExport]
[SupportedOSPlatform("browser")]
public static partial class MyWorker
{
    [JSExport]
    public static string Greet(string name) => $"Hello, {name}!";

    [JSExport]
    public static string GetUsers()
    {
        var users = new List<User> { new("Alice", 30), new("Bob", 25) };
        return JsonSerializer.Serialize(users);  // Serialize before returning
    }
}

public record User(string Name, int Age);

// In component
await using var worker = await WebWorkerClient.CreateAsync(JSRuntime);

// String result
var greeting = await worker.InvokeAsync<string>("MyApp.MyWorker.Greet", ["World"]);

// Complex result (automatically deserialized)
var users = await worker.InvokeAsync<List<User>>("MyApp.MyWorker.GetUsers", []);

Methods

public sealed class WebWorkerClient(IJSObjectReference worker) : IAsyncDisposable
{
    public static async Task<WebWorkerClient> CreateAsync(IJSRuntime jsRuntime);
    public async Task<TResult> InvokeAsync<TResult>(string method, object[] args, CancellationToken cancellationToken = default);
    public async ValueTask DisposeAsync();
}

Fixes dotnet/runtime#95452

@ilonatommy ilonatommy added this to the 11.0-preview1 milestone Jan 13, 2026
@ilonatommy ilonatommy self-assigned this Jan 13, 2026
@ilonatommy ilonatommy added the area-blazor Includes: Blazor, Razor Components label Jan 13, 2026
The webworker item template generates a .csproj file into the content/
folder during build. NuGet was discovering and trying to restore this
generated project, but it failed because:

1. The package Microsoft.AspNetCore.Components.WebAssembly version
   11.0.0-ci doesn't exist in NuGet feeds (it's built in this repo)
2. The generated project was being evaluated before the package was built

Fix: Add Web.ItemTemplates/content/**/*.proj to ProjectToExclude in
eng/Build.props, matching the existing exclusion for Web.ProjectTemplates.

Also simplified Directory.Build.props in the content folder to remove
the sources.props import which is no longer needed since the projects
are excluded from build.
The ByteOrderMarkTest requires all .razor files in templates to have
UTF-8 BOM (Byte Order Mark) for proper encoding detection.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new dotnet new webworker template that enables WebWorker support for Blazor WebAssembly applications, allowing compute-intensive .NET code to run off the main UI thread for improved responsiveness. The template provides both an infrastructure-only mode (--empty) and a full mode with demo code showcasing GitHub API integration.

Changes:

  • New WebWorker item template with client library, JavaScript worker infrastructure, and optional demo files
  • Integration with BlazorWeb template to optionally include WorkerClient project
  • Comprehensive README documentation and localization for 13 languages

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/.template.config/template.json Template configuration defining parameters, symbols, and post-actions
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/WebWorkerTemplate.WorkerClient/WorkerClient.cs C# client for communicating with WebWorker via JSInterop
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/WebWorkerTemplate.WorkerClient/wwwroot/worker.js JavaScript WebWorker entry point for loading .NET runtime
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/WebWorkerTemplate.WorkerClient/wwwroot/worker-client.js JavaScript client for managing worker lifecycle and message passing
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/Worker/GitHubWorker.cs Demo worker class showing GitHub API data processing
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/Pages/WebWorkerDemo.razor.cs Demo Blazor page comparing WebWorker vs UI thread performance
src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/Models/GitHubModels.cs Model classes for GitHub API responses
src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/.template.config/template.json Updated to reference WorkerClient project
src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWeb-CSharp/BlazorWebCSharp.1.sln Solution file with conditional WorkerClient project inclusion
src/ProjectTemplates/Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj Build configuration for generating WorkerClient.csproj
eng/Build.props Updated to exclude template content projects from build
src/ProjectTemplates/Web.ItemTemplates/.gitignore Ignore generated .csproj files
Multiple localization files Translated template descriptions for 13 languages

Comment thread src/ProjectTemplates/Web.ItemTemplates/content/WebWorker/Worker/GitHubWorker.cs Outdated
…harp/WorkerClient.cs

Co-authored-by: Daniel Roth <daroth@microsoft.com>
Comment thread src/ProjectTemplates/test/Templates.Blazor.Tests/WebWorkerTemplateTest.cs Outdated
@ilonatommy ilonatommy removed the pending-ci-rerun When assigned to a PR indicates that the CI checks should be rerun label Jan 29, 2026
@ilonatommy ilonatommy requested a review from javiercn February 4, 2026 08:32
Copy link
Copy Markdown
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

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

Looks good. Let's get it in for now and we'll get to play with it and refine it if necessary

@ilonatommy ilonatommy merged commit de6ec42 into dotnet:main Feb 6, 2026
25 checks passed
akoeplinger added a commit to dotnet/sdk that referenced this pull request Feb 15, 2026
Add .NET entry to approval baselines after dotnet/aspnetcore#65037
introduced the webworker template.
ilonatommy added a commit to dotnet/runtime that referenced this pull request Mar 19, 2026
…#125617)

## Summary

When debugging a Blazor WebAssembly app that uses Web Workers (via the
`dotnet new webworker` template, see
dotnet/aspnetcore#65037) in Visual Studio, the
worker thread hangs indefinitely during startup. The app never completes
initialization and the worker appears stuck at importing worker's
module.
```
await using var module = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./_content/WorkerLib/dotnet-web-worker-client.js");
```

This PR fixes two issues in the BrowserDebugProxy (`MonoProxy`) that
together cause the hang.

You can use this app to see the problem:
[BlazorAppRepro.zip](https://github.com/user-attachments/files/26027798/BlazorAppRepro.zip)

## Root Cause

**Issue 1 — Worker never resumed after Chrome pauses it on attach:**

When VS sends `Target.setAutoAttach({ waitForDebuggerOnStart: true })`,
Chrome pauses all new worker threads until the debugger explicitly sends
`Runtime.runIfWaitingForDebugger`. The proxy's `Target.attachedToTarget`
handler for workers only called `CreateWorkerExecutionContext()` but
never sent `Runtime.runIfWaitingForDebugger`, leaving the worker frozen
before any JS executed.

**Issue 2 — `Debugger.paused` event forwarded to IDE instead of being
resumed:**

Even after resuming the worker, VS's JavaScript debugger auto-attaches
with `breakOnLoad: true`, which fires a `Debugger.paused` event on the
worker session. In `OnDebuggerPaused`'s default case, when `JustMyCode`
is enabled and the execution context isn't runtime-ready
(`IsRuntimeReady == false`), the handler returns `false` — forwarding
the pause to VS. VS's JS debugger receives the pause but doesn't resume
it, leaving the worker stuck. Shortly after, the CDP connection crashes
with `ObjectDisposedException`.

## Changes

**`MonoProxy.cs`** — two targeted fixes:

1. **`Target.attachedToTarget` handler**: For worker targets, send
`Runtime.runIfWaitingForDebugger` after creating the worker execution
context. This tells Chrome to unpause the worker thread.

2. **`OnDebuggerPaused` default case**: When the session belongs to a
worker (detected via `context.ParentContext != null`) and the runtime
isn't ready yet, call `SendResume` instead of returning `false`. This
prevents the pause from being forwarded to the IDE and lets the worker
continue booting its WASM runtime.

## Testing

Manually verified with a Blazor WASM Standalone app + webworker library
project in VS 2022 (by replacing the .dlls VS uses):
- **Before fix**: F5 → worker hangs at startup, app stuck at "Creating
worker..."
- **After fix**: F5 → worker runs to completion, app shows correct
results, main-thread breakpoints (e.g., in `Home.razor`) hit correctly

## Known Limitation

Breakpoints in C# code running *inside* the Web Worker (e.g.,
`[JSExport]` methods in the worker library) do not get hit. The debug
proxy currently only attaches its SDB agent to the main page's WASM
runtime, not the worker's second runtime. This is a pre-existing
architectural limitation and is out of scope for this PR but I would
like to have it solved as well. Any ideas on how to do it are welcome.

Contributes to dotnet/aspnetcore#65823.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

.NET Web Worker docs, samples, and project template

5 participants