dotnet/aspnetcore · critical · Error
Worker .NET runtime not loaded
Error message
Worker .NET runtime not loaded
What it means
Thrown inside the worker's message listener when workerExports is empty at the time a method-call message arrives. workerExports is populated only after the .NET runtime (via the supplied dotnetJsUrl) successfully initializes the assembly and GETs its exports. An empty exports map means initialization never completed or failed; startupError holds the original init error if one exists.
Solutions
- Ensure the host posts the 'init' message first and awaits its acknowledgment before dispatching method calls.
- Inspect startupError (it is forwarded when set) — the underlying .NET runtime load error is the real cause.
- Verify dotnetJsUrl is reachable (network tab) and that assemblyName exactly matches the WASM build output.
- Check the worker's Content-Security-Policy allows 'wasm-unsafe-eval' and script-src for the runtime URL.
- Re-send an init message to retry; the current message will be rejected but a subsequent call will succeed once exports are populated.
Example fix
// before
self.postMessage({ method, args, requestId }); // before init → throws
// after — host waits for ready signal
const ready = new Promise(resolve => {
worker.addEventListener('message', e => { if (e.data.type === 'ready') resolve(); });
});
worker.postMessage({ type: 'init', dotnetJsUrl, assemblyName });
await ready;
worker.postMessage({ method, args, requestId }); Defensive patterns
Strategy: validation
Validate before calling
// Host-side: track init state before posting method calls
let initialized = false, initError: string | null = null;
worker.addEventListener('message', e => {
if (e.data.type === 'ready') initialized = true;
if (e.data.type === 'init-error') initError = e.data.error;
});
function assertReady() {
if (initError) throw new Error(`Worker init failed: ${initError}`);
if (!initialized) throw new Error('Worker not initialized — send init first');
} Type guard
type InitMessage = { type: 'init'; dotnetJsUrl: string; assemblyName: string };
type CallMessage = { method: string; args: unknown[]; requestId: string };
function isInitMessage(m: unknown): m is InitMessage {
return (m as any)?.type === 'init';
} Try / catch
// inside the worker's message listener:
if (Object.keys(workerExports).length === 0) {
self.postMessage({ type: 'result', requestId, error: startupError || 'Worker .NET runtime not loaded' });
return; // do not throw — surface to caller gracefully
} Prevention
- Always send the 'init' message before any method-call message.
- Surface a ready/error signal from the worker before the host sends calls.
- Verify dotnetJsUrl and assemblyName at host build time.
When it happens
Trigger: Posting a {method,args,requestId} message to the worker before posting the {type:'init', dotnetJsUrl, assemblyName} message; posting it after init failed (bad dotnetJsUrl, 404 on assemblies, WASM not supported); or a race where the method-call message arrives before initialize() resolves.
Common situations: Mis-ordered worker startup sequence from the host page; CDN/dotnetJsUrl returns 404; the assemblyName doesn't match a real assembly; the worker is reused after a failed init without re-initializing; browser blocks WASM via CSP.
Related errors
- Could not load settings from
- Descriptors must be assigned a renderer ID before getting…
- Failed to retrieve token, no account found.
- ' ' already initialized.
- ' ' has not been initialized.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/6884e303126c7c9e.
Report an issue: GitHub.
Appendix: source
Thrown at src/ProjectTemplates/Web.ProjectTemplates/content/BlazorWebWorker-CSharp/wwwroot/dotnet-web-worker.js:35
} catch (err) {
const errorMessage = err?.message ?? String(err);
startupError = errorMessage;
console.error("[Worker] Failed to initialize .NET:", err);
self.postMessage({ type: "ready", error: errorMessage });
}
}
self.addEventListener('message', async (e) => {
if (e.data.type === 'init') {
await initialize(e.data.dotnetJsUrl, e.data.assemblyName);
return;
}
const { method, args, requestId } = e.data;
try {
if (Object.keys(workerExports).length === 0) {
throw new Error(startupError || "Worker .NET runtime not loaded");
}
const fn = method.split('.').reduce((obj, part) => obj?.[part], workerExports);
if (typeof fn !== 'function') {
throw new Error(`Method not found: ${method}`);
}
const result = await fn(...args);
self.postMessage({ type: "result", requestId, result }, collectTransferables(result));
} catch (err) {
self.postMessage({ type: "result", requestId, error: err?.message ?? String(err) });
}
});
function collectTransferables(value) {
if (ArrayBuffer.isView(value)) return [value.buffer];
if (value instanceof ArrayBuffer) return [value];
return [];View on GitHub (pinned to 3600ca084e)