dotnet/aspnetcore · error · InvalidOperationException

The subscriber with the given section ID '{identifier}' is a

Error message

The subscriber with the given section ID '{identifier}' is already unsubscribed.

What it means

Thrown by SectionRegistry.Unsubscribe when Dictionary.Remove returns false — i.e. the identifier was never a subscriber key, or was already removed. Unsubscribe is called from SectionOutlet.Dispose and from SectionOutlet.SetParametersAsync when the identifier changes; the throw indicates an unsubscribe without a matching subscribe.

Source

Thrown at src/Components/Components/src/Sections/SectionRegistry.cs:85

        if (_subscribersByIdentifier.ContainsKey(identifier))
        {
            throw new InvalidOperationException($"There is already a subscriber to the content with the given section ID '{identifier}'.");
        }

        // Notify the new subscriber with any existing content.
        var provider = GetCurrentProviderContentOrDefault(identifier);
        subscriber.ContentUpdated(provider);

        _subscribersByIdentifier.Add(identifier, subscriber);

        DetectRenderModeMismatch(identifier, subscriber, provider);
    }

    public void Unsubscribe(object identifier)
    {
        if (!_subscribersByIdentifier.Remove(identifier))
        {
            throw new InvalidOperationException($"The subscriber with the given section ID '{identifier}' is already unsubscribed.");
        }

        // The section pair is no longer complete, so re-arm the mismatch diagnostic in case the outlet
        // and content are later reconnected in mismatched render modes.
        _mismatchLoggedIdentifiers?.Remove(identifier);
    }

    public void NotifyContentProviderChanged(object identifier, SectionContent provider)
    {
        if (!_providersByIdentifier.TryGetValue(identifier, out var providers))
        {
            throw new InvalidOperationException($"There are no content providers with the given section ID '{identifier}'.");
        }

        // We only notify content changed for subscribers when the content of the
        // most recently added provider changes.
        if (providers.Count != 0 && providers[^1] == provider)
        {

View on GitHub (pinned to 3600ca084e)

Solutions

  1. Do not call Dispose on SectionOutlet instances yourself; let the renderer own the lifecycle.
  2. If you hold a reference and must dispose defensively, null-out _subscribedIdentifier tracking in your own wrapper so Dispose is not re-entrant.
  3. Reproduce with Blazor's diagnostic logging enabled (Microsoft.AspNetCore.Components at Debug) to see whether SetParametersAsync or a prior Dispose already ran for the same outlet.
  4. Check for custom IComponent implementations that wrap SectionOutlet and forward Dispose incorrectly.

Example fix

// before: manual double-dispose of an outlet-holding component
outletHolder.Dispose();
outletHolder.Dispose(); // SectionOutlet.Dispose fires twice -> [343]

// after: idempotent dispose in the holder
private bool _disposed;
public void Dispose() { if (_disposed) return; _disposed = true; _outlet?.Dispose(); }
Defensive patterns

Strategy: validation

Validate before calling

// If you wrap a SectionOutlet, track your own disposed flag:
private int _disposed; // Interlocked
public void Dispose() {
    if (Interlocked.Exchange(ref _disposed, 1) == 0) _outlet.Dispose();
}

Prevention

When it happens

Trigger: SectionOutlet.Dispose runs twice, or SetParametersAsync tries to unsubscribe from _subscribedIdentifier that was never subscribed (e.g. Attach never completed, or Dispose already ran).

Common situations: Disposing a SectionOutlet more than once from user code; a SectionOutlet whose SetParametersAsync never succeeded (so _subscribedIdentifier is set but Subscribe threw) and whose Dispose then tries to Unsubscribe; renderer disposal ordering edge cases during prerendering teardown or hot reload.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/a416f08828f82c4f. Report an issue: GitHub.