dotnet/aspnetcore · error · InvalidOperationException
State already persisted.
Error message
State already persisted.
What it means
ComponentStatePersistenceManager.PersistStateAsync enforces a single-persistence contract via the _stateIsPersisted flag. Once PersistStateAsync has completed (the flag is set true at the end of PauseAndPersistState), a second call throws. Persistence is a destructive, terminal operation: it pauses state, drains registered callbacks into _currentState, writes to the store, and clears working buffers, so repeating it would produce partial or empty output.
Solutions
- Ensure PersistStateAsync is invoked exactly once per manager lifetime; track persistence state in your host code if multiple paths could reach it.
- For repeated persistence (e.g., tests), create a new ComponentStatePersistenceManager for each cycle.
- Audit middleware/endpoint order so only the framework-owned persistence hook calls PersistStateAsync.
Example fix
// before: persisted twice await manager.PersistStateAsync(store, renderer); await manager.PersistStateAsync(store, renderer); // throws // after: single persistence, or new manager per cycle await manager.PersistStateAsync(store, renderer); // for a new cycle, construct a fresh manager
Defensive patterns
Strategy: validation
Validate before calling
if (!_persisted)
{
await manager.PersistStateAsync(store, renderer);
_persisted = true;
} Prevention
- Invoke PersistStateAsync exactly once per render lifecycle.
- Audit middleware/endpoint order so only one path persists.
- In integration tests, create a new manager for each persist cycle.
When it happens
Trigger: Calling PersistStateAsync(store, renderer) twice on the same manager instance. A host/endpoint pipeline that runs the persist step more than once for a single render (e.g., both a response-writing middleware and a developer's manual call). Persisting after a composite-store persistence loop already completed.
Common situations: Adding custom middleware that also persists component state alongside the framework's built-in persistence. Endpoints that render and persist, then a fallback path re-invokes persistence on exception handling. Integration tests that reuse a manager across multiple render-then-persist cycles.
Related errors
- Cannot update existing state: previous state has not been…
- PersistentComponentState already initialized.
- Persisting state is only allowed during an OnPersisting…
- Registering a callback while persisting state is not…
- State already initialized.
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/bf4dba6a50bf5325.
Report an issue: GitHub.
Appendix: 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 3600ca084e)