dotnet/aspnetcore · error · Error

The circuit associated with this dispatcher is no longer ava

Error message

The circuit associated with this dispatcher is no longer available.

What it means

Thrown by throwIfDispatchingWhenDisposed() — invoked from beginInvokeDotNetFromJS, endInvokeJSFromDotNet, and sendByteArray — when _disposed is true. Once a circuit is disposed, the underlying SignalR connection is gone, so any JS->.NET interop dispatch is rejected because there is no server endpoint to receive it.

Source

Thrown at src/Components/Web.JS/src/Platform/Circuits/CircuitManager.ts:528

  // Implements DotNet.DotNetCallDispatcher
  public endInvokeJSFromDotNet(asyncHandle: number, succeeded: boolean, argsJson: any): void {
    this.throwIfDispatchingWhenDisposed();
    if (asyncHandle !== 0) {
      this.changeActivity(-1);
    }
    this._connection!.send('EndInvokeJSFromDotNet', asyncHandle, succeeded, argsJson);
  }

  // Implements DotNet.DotNetCallDispatcher
  public sendByteArray(id: number, data: Uint8Array): void {
    this.throwIfDispatchingWhenDisposed();
    this._connection!.send('ReceiveByteArray', id, data);
  }

  private throwIfDispatchingWhenDisposed() {
    if (this._disposed) {
      throw new Error('The circuit associated with this dispatcher is no longer available.');
    }
  }

  public sendLocationChanged(uri: string, state: string | undefined, intercepted: boolean): Promise<void> {
    return this._connection!.send('OnLocationChanged', uri, state, intercepted);
  }

  public sendLocationChanging(callId: number, uri: string, state: string | undefined, intercepted: boolean): Promise<void> {
    return this._connection!.send('OnLocationChanging', callId, uri, state, intercepted);
  }

  public sendJsDataStream(data: ArrayBufferView | Blob, streamId: number, chunkSize: number) {
    this.changeActivity(1);
    return sendJSDataStream(this._connection!, data, streamId, chunkSize, () => this.changeActivity(-1));
  }

  public resolveElement(sequenceOrIdentifier: string): LogicalElement {
    // It may be a root component added by JS

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Cancel/short-circuit JS work before disposing the circuit.
  2. Guard interop calls with a disposed flag in your JS interop wrappers.
  3. On the .NET side, catch OperationCanceledException / JSDisconnectedException from interop and treat as expected during teardown.

Example fix

// before
await dotNetRef.invokeMethodAsync('OnDone'); // throws if circuit disposed

// after
if (!disposed) {
  try { await dotNetRef.invokeMethodAsync('OnDone'); }
  catch { /* circuit gone, ignore */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Track disposal in JS and short-circuit interop.
let disposed = false;
circuitManager.dispose(); disposed = true;
if (!disposed) { await dotNetRef.invokeMethodAsync('DoWork'); }

Try / catch

try { await dotNetRef.invokeMethodAsync('OnDone'); }
catch (e) {
  // circuit gone during async work — expected during teardown
  if (/no longer available|disposed/i.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: Firing a JS interop call (invokeMethodAsync from JS, or a pending DotNet callback) after the circuit was disposed — e.g. user navigated away, tab closed, circuit dropped, or dispose() ran while async interop was still queued.

Common situations: Long-running JS tasks completing after navigation/circuit loss; event handlers firing during teardown; cleanup callbacks invoking .NET methods on a disposed circuit.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/83da029c2d66ed0f. Report an issue: GitHub.