dotnet/aspnetcore · error · InvalidOperationException

State already persisted.

Error message

State already persisted.

What it means

Thrown by ComponentStatePersistenceManager.PersistStateAsync when _stateIsPersisted is true. The flag is set at the end of PauseAndPersistState and never reset, so the manager is single-use for persistence within its lifetime.

Source

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

        else
        {
            State.InitializeExistingState(data, context);
            _servicesRegistry?.RegisterForPersistence(State);
            _stateIsInitialized = true;
        }
    }

    /// <summary>
    /// Persists the component application state into the given <see cref="IPersistentComponentStateStore"/>.
    /// </summary>
    /// <param name="store">The <see cref="IPersistentComponentStateStore"/> to restore the application state from.</param>
    /// <param name="renderer">The <see cref="Renderer"/> that components are being rendered.</param>
    /// <returns>A <see cref="Task"/> that will complete when the state has been restored.</returns>
    public Task PersistStateAsync(IPersistentComponentStateStore store, Renderer renderer)
    {
        if (_stateIsPersisted)
        {
            throw new InvalidOperationException("State already persisted.");
        }

        return renderer.Dispatcher.InvokeAsync(PauseAndPersistState);

        async Task PauseAndPersistState()
        {
            State.PersistingState = true;

            if (store is IEnumerable<IPersistentComponentStateStore> compositeStore)
            {
                // We only need to do inference when there is more than one store. This is determined by
                // the set of rendered components.
                InferRenderModes(renderer);

                // Iterate over each store and give it a chance to run against the existing declared
                // render modes. After we've run through a store, we clear the current state so that
                // the next store can start with a clean slate.
                foreach (var store in compositeStore)

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Persist once per manager/circuit lifetime; guard with your own flag.
  2. Use a new ComponentStatePersistenceManager (new DI scope) for each persistence cycle if you need to persist again.
  3. In hosted scenarios rely on the framework's single PersistStateAsync call during prerender rather than calling it yourself again.
  4. Audit the request pipeline for duplicate persist invocations.

Example fix

// before
await manager.PersistStateAsync(store, renderer); // called twice -> throws

// after
if (!_persisted)
{
    await manager.PersistStateAsync(store, renderer);
    _persisted = true;
}
Defensive patterns

Strategy: validation

Validate before calling

private bool _persisted;

async Task PersistOnceAsync(ComponentStatePersistenceManager manager,
    IPersistentComponentStateStore store, Renderer renderer)
{
    if (_persisted) return;
    await manager.PersistStateAsync(store, renderer);
    _persisted = true;
}

Try / catch

try
{
    await manager.PersistStateAsync(store, renderer);
}
catch (InvalidOperationException ex) when (ex.Message == "State already persisted.")
{
    // already persisted this cycle - nothing to do
}

Prevention

When it happens

Trigger: Calling PersistStateAsync(store, renderer) twice on the same manager instance.

Common situations: Middleware/endpoints invoking persist on each render without a fresh manager; tests reusing the manager; an integration that triggers persist both for prerender and for the final response.

Related errors


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