Skip to content

Commit b9daff6

Browse files
ilonatommyguardrex
andauthored
[.net11] Use the new webworker template instead of samples (#36871)
* Update docs for .net11 -> now it's easier to use the new template than refer to sample apps etc. * Updates --------- Co-authored-by: Luke Latham <1622880+guardrex@users.noreply.github.com>
1 parent 31c17d2 commit b9daff6

2 files changed

Lines changed: 206 additions & 2 deletions

File tree

aspnetcore/blazor/blazor-with-dotnet-on-web-workers.md

Lines changed: 199 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
---
22
title: ASP.NET Core Blazor with .NET on Web Workers
3+
ai-usage: ai-assisted
34
author: guardrex
45
description: Learn how to use Web Workers to enable JavaScript to run on separate threads that don't block the main UI thread for improved app performance in a Blazor WebAssembly app.
56
monikerRange: '>= aspnetcore-8.0'
67
ms.author: wpickett
78
ms.custom: mvc
8-
ms.date: 11/20/2025
9+
ms.date: 03/13/2026
910
uid: blazor/blazor-web-workers
1011
---
1112
# ASP.NET Core Blazor with .NET on Web Workers
@@ -18,6 +19,201 @@ uid: blazor/blazor-web-workers
1819

1920
Modern Blazor WebAssembly apps often handle CPU-intensive work alongside rich UI updates. Tasks such as image processing, document parsing, or data crunching can easily freeze the browser's main thread. Web Workers let you push that work to a background thread. Combined with the .NET WebAssembly runtime, you can keep writing C# while the UI stays responsive.
2021

22+
:::moniker range=">= aspnetcore-11.0"
23+
24+
The `webworker` project template provides built-in scaffolding for running .NET code in a Web Worker. The template generates the required JavaScript worker scripts and a C# `WebWorkerClient` class, which removes the need to write the interop layer manually. To learn about Web Workers with React, see <xref:client-side/dotnet-on-webworkers>.
25+
26+
> [!NOTE]
27+
> The `webworker` template isn't limited to Blazor. The template works with any .NET WebAssembly host, including standalone `wasmbrowser` apps and custom JavaScript frontends, such as React or vanilla JS. In non-Blazor scenarios, import the template's JavaScript client (`dotnet-web-worker-client.js`) directly from your entry point and call `[JSExport]` methods without the Blazor-specific C# `WebWorkerClient` class.
28+
29+
## Create the projects
30+
31+
Create a Blazor WebAssembly app and a .NET Web Worker class library:
32+
33+
```dotnetcli
34+
dotnet new blazorwasm -n SampleApp
35+
dotnet new webworker -n WebWorker
36+
```
37+
38+
Add a project reference from the app to the worker library:
39+
40+
```dotnetcli
41+
cd SampleApp
42+
dotnet add reference ../WebWorker/WebWorker.csproj
43+
```
44+
45+
## Enable `AllowUnsafeBlocks`
46+
47+
Enable the <xref:Microsoft.Build.Tasks.Csc.AllowUnsafeBlocks> property in the app's project file (`SampleApp.csproj`), which is required for [`[JSExport]` attribute](xref:System.Runtime.InteropServices.JavaScript.JSExportAttribute) usage:
48+
49+
```xml
50+
<PropertyGroup>
51+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
52+
</PropertyGroup>
53+
```
54+
55+
> [!WARNING]
56+
> The JS interop API requires enabling <xref:Microsoft.Build.Tasks.Csc.AllowUnsafeBlocks>. Be careful when implementing your own unsafe code in .NET apps, which can introduce security and stability risks. For more information, see [Unsafe code, pointer types, and function pointers](/dotnet/csharp/language-reference/unsafe-code).
57+
58+
## Define worker methods
59+
60+
Worker methods are `static` methods marked with [`[JSExport]`](xref:System.Runtime.InteropServices.JavaScript.JSExportAttribute) in a `static partial class`. Define them in the main application project because the assembly name must match the one used by the worker runtime.
61+
62+
Due to `[JSExport]` limitations, worker methods can only return primitives or strings. For complex types, serialize to JSON before returning. The `WebWorkerClient` automatically deserializes JSON results.
63+
64+
`Worker.cs`:
65+
66+
```csharp
67+
using System.Runtime.InteropServices.JavaScript;
68+
using System.Runtime.Versioning;
69+
using System.Text.Json;
70+
71+
[SupportedOSPlatform("browser")]
72+
public static partial class Worker
73+
{
74+
[JSExport]
75+
public static string Greet(string name) => $"Hello, {name}!";
76+
77+
[JSExport]
78+
public static string GetUsers()
79+
{
80+
var users = new List<User> { new("Alice", 30), new("Bob", 25) };
81+
return JsonSerializer.Serialize(users);
82+
}
83+
}
84+
85+
public record User(string Name, int Age);
86+
```
87+
88+
## Use the worker from a component
89+
90+
Inject `IJSRuntime` and use `WebWorkerClient.CreateAsync` to create a worker instance. The client manages the JavaScript messaging layer on your behalf.
91+
92+
`Pages/Home.razor`:
93+
94+
```razor
95+
@page "/"
96+
@using WebWorker
97+
@implements IAsyncDisposable
98+
@inject IJSRuntime JSRuntime
99+
100+
<PageTitle>Home</PageTitle>
101+
102+
<h1>Web Worker demo</h1>
103+
104+
<button class="btn btn-primary" @onclick="CallWorker" disabled="@(worker is null)">
105+
Call Worker
106+
</button>
107+
108+
@if (!string.IsNullOrEmpty(greeting))
109+
{
110+
<p>@greeting</p>
111+
}
112+
113+
@if (users is not null)
114+
{
115+
<ul>
116+
@foreach (var user in users)
117+
{
118+
<li>@user.Name (age @user.Age)</li>
119+
}
120+
</ul>
121+
}
122+
```
123+
124+
`Pages/Home.razor.cs`:
125+
126+
```csharp
127+
using System.Runtime.Versioning;
128+
using Microsoft.AspNetCore.Components;
129+
using Microsoft.JSInterop;
130+
using WebWorker;
131+
132+
namespace SampleApp.Pages;
133+
134+
[SupportedOSPlatform("browser")]
135+
public partial class Home : ComponentBase, IAsyncDisposable
136+
{
137+
private WebWorkerClient? worker;
138+
private string greeting = string.Empty;
139+
private List<User>? users;
140+
141+
protected override async Task OnAfterRenderAsync(bool firstRender)
142+
{
143+
if (firstRender)
144+
{
145+
worker = await WebWorkerClient.CreateAsync(JSRuntime);
146+
StateHasChanged();
147+
}
148+
}
149+
150+
private async Task CallWorker()
151+
{
152+
if (worker is null)
153+
{
154+
return;
155+
}
156+
157+
greeting = await worker.InvokeAsync<string>(
158+
"SampleApp.Worker.Greet", ["World"]);
159+
160+
users = await worker.InvokeAsync<List<User>>(
161+
"SampleApp.Worker.GetUsers", []);
162+
}
163+
164+
public async ValueTask DisposeAsync()
165+
{
166+
if (worker is not null)
167+
{
168+
await worker.DisposeAsync();
169+
}
170+
}
171+
}
172+
```
173+
174+
## Template output
175+
176+
The `dotnet new webworker` template generates a class library with the following structure:
177+
178+
```
179+
WebWorker/
180+
├── WebWorker.csproj
181+
├── WebWorkerClient.cs
182+
└── wwwroot/
183+
├── dotnet-web-worker-client.js
184+
└── dotnet-web-worker.js
185+
```
186+
187+
* `WebWorkerClient.cs`: C# client that manages worker lifecycle and communication.
188+
* `dotnet-web-worker-client.js`: JavaScript class that creates the worker, dispatches messages, and resolves pending requests.
189+
* `dotnet-web-worker.js`: Worker entry point that boots the .NET WebAssembly runtime and dynamically resolves `[JSExport]` methods by name.
190+
191+
## `WebWorkerClient` API
192+
193+
The `WebWorkerClient` class exposes an async API for communicating with a Web Worker:
194+
195+
```csharp
196+
public sealed class WebWorkerClient : IAsyncDisposable
197+
{
198+
public static async Task<WebWorkerClient> CreateAsync(
199+
IJSRuntime jsRuntime);
200+
201+
public async Task<TResult> InvokeAsync<TResult>(
202+
string method, object[] args,
203+
CancellationToken cancellationToken = default);
204+
205+
public async ValueTask DisposeAsync();
206+
}
207+
```
208+
209+
* `CreateAsync`: Initializes the worker and waits for the .NET runtime to be ready inside the worker thread.
210+
* `InvokeAsync<TResult>`: Calls a `[JSExport]` method on the worker by its full name (`Namespace.ClassName.MethodName`) and returns the deserialized result. JSON string results are automatically parsed into `TResult`.
211+
* `DisposeAsync`: Terminates the worker and releases resources. Use `await using` or call explicitly.
212+
213+
:::moniker-end
214+
215+
:::moniker range="< aspnetcore-11.0"
216+
21217
The guidance in this article mirrors the concepts from the React-focused *.NET on Web Workers* walkthrough, but adapts every step to a Blazor frontend. It highlights the same QR-code generation scenario implemented in this repository. To learn about Web Workers with React, see <xref:client-side/dotnet-on-webworkers>.
22218

23219
## Sample app
@@ -314,6 +510,8 @@ public partial class Home : ComponentBase
314510
* Move long-running workflows into dedicated worker instances per feature area.
315511
* Explore shared array buffers or Atomics when you need higher-throughput synchronization between Blazor and workers.
316512

513+
:::moniker-end
514+
317515
## Additional resources
318516

319517
<xref:client-side/dotnet-on-webworkers>

aspnetcore/client-side/dotnet-on-webworkers.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
---
22
title: .NET on Web Workers
3+
ai-usage: ai-assisted
34
author: guardrex
45
description: Learn how to use Web Workers to enable JavaScript to run on separate threads that don't block the main UI thread for improved app performance in a React app.
56
monikerRange: '>= aspnetcore-8.0'
67
ms.author: wpickett
78
ms.custom: mvc
8-
ms.date: 11/20/2025
9+
ms.date: 03/13/2026
910
uid: client-side/dotnet-on-webworkers
1011
---
1112
# .NET on Web Workers
@@ -20,6 +21,11 @@ Modern web apps often require intensive computational tasks that can block the m
2021

2122
This approach is particularly valuable when you need to perform complex calculations, data processing, or business logic without requiring direct DOM manipulation. Instead of rewriting algorithms in JS, you can maintain your existing .NET codebase and execute it efficiently in the background while your React.js frontend remains responsive.
2223

24+
> [!TIP]
25+
> Starting with .NET 11, the `webworker` project template (`dotnet new webworker`) scaffolds the JavaScript worker scripts and interop boilerplate for you. The template works with any .NET WebAssembly host—Blazor, standalone `wasmbrowser` apps, and custom JavaScript frontends like React. Import the template's JavaScript client (`dotnet-web-worker-client.js`) directly from your app's entry point to get started. For the Blazor-specific integration that includes a C# `WebWorkerClient` class, see <xref:blazor/blazor-web-workers>.
26+
27+
This article demonstrates the manual approach for React.js frontends using a standalone .NET WebAssembly project.
28+
2329
## Sample app
2430

2531
Explore a complete working implementation in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). The sample is available for .NET 10 or later and named `DotNetOnWebWorkersReact`.

0 commit comments

Comments
 (0)