dotnet/aspnetcore · error · InvalidOperationException

The registered callback

Error message

The registered callback {registration.Callback.Method.Name} must be associated with a component or define an explicit render mode type during registration.

What it means

During InferRenderModes (run when persisting against a composite/multi-store), the manager walks registered persistence callbacks. For each callback whose explicit RenderMode is null, it tries to infer the mode from the callback's Target when it is an IComponent. If the Target is not an IComponent and no explicit RenderMode was supplied at registration time, inference is impossible and the manager throws. The callback cannot be routed to the correct store(s) without a known render mode.

Solutions

  1. Pass an explicit render mode when registering the callback from a non-component target, using the registration overload that accepts an IComponentRenderMode.
  2. Move the persistence call so its callback Target is the IComponent instance (e.g., call from within a component's lifecycle method).
  3. If the value genuinely applies to SSR only, register it through a single (non-composite) store path so InferRenderModes never runs.

Example fix

// before: persist from a service with no render mode in a multi-store app
state.PersistAsJson("my-key", value);

// after: pass explicit render mode, or call from the component
state.PersistAsJson("my-key", value, RenderMode.InteractiveServer);
Defensive patterns

Strategy: validation

Validate before calling

// When persisting from a non-component, pass an explicit render mode.
if (this is not IComponent)
{
    state.PersistAsJson(key, value, RenderMode.InteractiveServer);
}
else
{
    state.PersistAsJson(key, value);
}

Prevention

When it happens

Trigger: Registering a persistence callback whose target is a non-component object (e.g., a service, a static method, or a lambda not bound to an IComponent) via PersistentComponentState.PersistAsJson or a similar API, then persisting through a composite store (a store implementing IEnumerable<IPersistentComponentStateStore>) which triggers InferRenderModes. The registration did not pass an explicit render mode.

Common situations: A service or helper class calls state.PersistAsJson(key, value) during prerendering in a Blazor Web App with multiple render modes (Server + WebAssembly). Persisting from a non-component subscriber (e.g., a PersistentValueProviderComponentSubscription whose backing object is not the component). Custom state-persistence extensions that register callbacks from services.

Related errors


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

Appendix: source

Thrown at src/Components/Components/src/PersistentState/ComponentStatePersistenceManager.cs:206

            }

            if (registration.Callback.Target is IComponent component)
            {
                var componentRenderMode = renderer.GetComponentRenderMode(component);
                if (componentRenderMode != null)
                {
                    _registeredCallbacks[i] = new PersistComponentStateRegistration(registration.Callback, componentRenderMode);
                }
                else
                {
                    // If we can't find a render mode, it's an SSR only component and we don't need to
                    // persist its state at all.
                    _registeredCallbacks[i] = default;
                }
                continue;
            }

            throw new InvalidOperationException(
                $"The registered callback {registration.Callback.Method.Name} must be associated with a component or define" +
                $" an explicit render mode type during registration.");
        }
    }

    internal Task<bool> TryPauseAsync(IPersistentComponentStateStore store)
    {
        List<Task<bool>>? pendingCallbackTasks = null;

        // We are iterating backwards to allow the callbacks to remove themselves from the list.
        // Otherwise, we would have to make a copy of the list to avoid running into situations
        // where we don't run all the callbacks because the count of the list changed while we
        // were iterating over it.
        // It is not allowed to register a callback while we are persisting the state, so we don't
        // need to worry about new callbacks being added to the list.
        for (var i = _registeredCallbacks.Count - 1; i >= 0; i--)
        {
            var registration = _registeredCallbacks[i];

View on GitHub (pinned to 3600ca084e)