From 66d0e390fd92ee85d2e054a587ef001b400247f8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 19 Jun 2026 13:22:47 +0200 Subject: [PATCH 1/4] Pause on when tab is hidden. --- aspnetcore/blazor/state-management/server.md | 67 ++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/aspnetcore/blazor/state-management/server.md b/aspnetcore/blazor/state-management/server.md index cdf44deb59f3..bd9690891e44 100644 --- a/aspnetcore/blazor/state-management/server.md +++ b/aspnetcore/blazor/state-management/server.md @@ -185,6 +185,73 @@ window.addEventListener('visibilitychange', () => { :::moniker range=">= aspnetcore-11.0" +## Automatic circuit pause on tab inactivity + +The framework can optionally pause a circuit when the browser tab becomes hidden, freeing server memory and SignalR connections held by inactive users. Enable auto-pause using the `ConfigureBrowser` component in `App.razor`: + +```razor + +``` + +After the tab is hidden for `HiddenDelayMilliseconds` (default: 120,000 ms), the circuit pauses. If the user returns before the delay elapses, the pause doesn't happen. + +The framework defers the pause while circuit-owned work is in progress (downloads, uploads, JS interop calls, Web Locks, Picture-in-Picture). It vetoes the pause entirely while focused text inputs with Blazor `@bind` bindings are edited or audio/video is playing. + +For elements without Blazor bindings (for example, ``, WebRTC connections, or custom elements), the app is responsible for handling state. Use `onPauseRequested` in the [Blazor startup configuration](xref:blazor/fundamentals/startup): + +```razor + +``` + +> [!NOTE] +> The `` element can't have its value restored after pause/resume due to browser security restrictions. Using `[PersistentState]` on a property bound to a file input causes an `InvalidStateError` that crashes the circuit. Instead, capture the file name in a separate property: +> +> ```razor +> +> @SelectedFileName +> +> @code { +> [PersistentState(AllowUpdates = true)] +> public string? SelectedFileName { get; set; } +> +> private void HandleFileSelected(InputFileChangeEventArgs e) +> { +> SelectedFileName = e.File.Name; +> } +> } +> ``` + +:::moniker-end + +:::moniker range=">= aspnetcore-11.0" + ## Server-triggered circuit pause A server-side Blazor app that adopts the Interactive Server render mode can implement server-triggered circuit pause, which allows the app to gracefully pause client circuits, preserving client state for seamless reconnection. From 72af3710258133339a0dfd919747a116bd213689 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 23 Jun 2026 13:20:27 +0200 Subject: [PATCH 2/4] Mobile app backgrounding is not supported. --- aspnetcore/blazor/state-management/server.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/aspnetcore/blazor/state-management/server.md b/aspnetcore/blazor/state-management/server.md index bd9690891e44..a32e854e92fe 100644 --- a/aspnetcore/blazor/state-management/server.md +++ b/aspnetcore/blazor/state-management/server.md @@ -6,7 +6,7 @@ description: Learn how to persist user data (state) in server-side Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 11/11/2025 +ms.date: 06/23/2026 uid: blazor/state-management/server --- # ASP.NET Core Blazor server-side state management @@ -201,6 +201,14 @@ The framework can optionally pause a circuit when the browser tab becomes hidden After the tab is hidden for `HiddenDelayMilliseconds` (default: 120,000 ms), the circuit pauses. If the user returns before the delay elapses, the pause doesn't happen. +> [!NOTE] +> Auto-pause triggers on the [Page Visibility API](https://developer.mozilla.org/docs/Web/API/Page_Visibility_API) `visibilitychange` event, whose meaning differs by platform: +> +> * On desktop, the tab becomes hidden when the user switches tabs or minimizes the window. The pause timer runs reliably and the circuit pauses gracefully after the delay. +> * On mobile, the page also becomes hidden when the *whole app* is backgrounded (switching apps, returning to the home screen, or locking the screen), not just when switching browser tabs. +> +> On mobile, the operating system suspends the page's JavaScript shortly after the app is backgrounded (within seconds on Android, up to about 30 seconds on iOS). If `HiddenDelayMilliseconds` is longer than that window, the pause timer never fires and the circuit is dropped by the OS-initiated disconnect instead of pausing gracefully. The session is still preserved through the normal reconnection and [circuit state persistence](#circuit-state-persistence) path, but the client-side veto and deferral logic doesn't run. For this reason, graceful auto-pause isn't guaranteed and isn't a supported scenario on mobile when the app is backgrounded. + The framework defers the pause while circuit-owned work is in progress (downloads, uploads, JS interop calls, Web Locks, Picture-in-Picture). It vetoes the pause entirely while focused text inputs with Blazor `@bind` bindings are edited or audio/video is playing. For elements without Blazor bindings (for example, ``, WebRTC connections, or custom elements), the app is responsible for handling state. Use `onPauseRequested` in the [Blazor startup configuration](xref:blazor/fundamentals/startup): From 8a4b98827c66720db2668e6c8cc62cbd3eeb03d8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 14 Jul 2026 11:23:44 +0200 Subject: [PATCH 3/4] The feature is in package now, update the doc. --- aspnetcore/blazor/state-management/server.md | 74 +++++++++++--------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/aspnetcore/blazor/state-management/server.md b/aspnetcore/blazor/state-management/server.md index a32e854e92fe..129380e30c50 100644 --- a/aspnetcore/blazor/state-management/server.md +++ b/aspnetcore/blazor/state-management/server.md @@ -6,7 +6,7 @@ description: Learn how to persist user data (state) in server-side Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/23/2026 +ms.date: 07/14/2026 uid: blazor/state-management/server --- # ASP.NET Core Blazor server-side state management @@ -187,19 +187,19 @@ window.addEventListener('visibilitychange', () => { ## Automatic circuit pause on tab inactivity -The framework can optionally pause a circuit when the browser tab becomes hidden, freeing server memory and SignalR connections held by inactive users. Enable auto-pause using the `ConfigureBrowser` component in `App.razor`: +The framework can optionally pause a circuit when the browser tab becomes hidden, freeing server memory and SignalR connections held by inactive users. Auto-pause is provided by the `Microsoft.AspNetCore.Components.Server.AutoPause` package. After adding a package reference, enable the feature by calling `AddAutoPause` when the app's root component is mapped: -```razor - +```csharp +app.MapRazorComponents() + .WithBrowserOptions(options => options.AddAutoPause(p => p.HiddenDelay = TimeSpan.FromSeconds(30))); ``` -After the tab is hidden for `HiddenDelayMilliseconds` (default: 120,000 ms), the circuit pauses. If the user returns before the delay elapses, the pause doesn't happen. +`AddAutoPause` configures an `AutoPauseBrowserOptions` instance with the following properties: + +* `Enabled`: Whether auto-pause is enabled. Defaults to `true`, so calling `AddAutoPause` is what enables the feature. +* `HiddenDelay`: The delay after the tab becomes hidden before the circuit pauses. Defaults to two minutes (`TimeSpan.FromMinutes(2)`) and must be greater than `TimeSpan.Zero`. + +After the tab is hidden for `HiddenDelay`, the circuit pauses. If the user returns before the delay elapses, the pause doesn't happen. > [!NOTE] > Auto-pause triggers on the [Page Visibility API](https://developer.mozilla.org/docs/Web/API/Page_Visibility_API) `visibilitychange` event, whose meaning differs by platform: @@ -207,54 +207,64 @@ After the tab is hidden for `HiddenDelayMilliseconds` (default: 120,000 ms), the > * On desktop, the tab becomes hidden when the user switches tabs or minimizes the window. The pause timer runs reliably and the circuit pauses gracefully after the delay. > * On mobile, the page also becomes hidden when the *whole app* is backgrounded (switching apps, returning to the home screen, or locking the screen), not just when switching browser tabs. > -> On mobile, the operating system suspends the page's JavaScript shortly after the app is backgrounded (within seconds on Android, up to about 30 seconds on iOS). If `HiddenDelayMilliseconds` is longer than that window, the pause timer never fires and the circuit is dropped by the OS-initiated disconnect instead of pausing gracefully. The session is still preserved through the normal reconnection and [circuit state persistence](#circuit-state-persistence) path, but the client-side veto and deferral logic doesn't run. For this reason, graceful auto-pause isn't guaranteed and isn't a supported scenario on mobile when the app is backgrounded. +> On mobile, the operating system suspends the page's JavaScript shortly after the app is backgrounded (within seconds on Android, up to about 30 seconds on iOS). If `HiddenDelay` is longer than that window, the pause timer never fires and the circuit is dropped by the OS-initiated disconnect instead of pausing gracefully. The session is still preserved through the normal reconnection and [circuit state persistence](#circuit-state-persistence) path, but the client-side veto and deferral logic doesn't run. For this reason, graceful auto-pause isn't guaranteed and isn't a supported scenario on mobile when the app is backgrounded. The framework defers the pause while circuit-owned work is in progress (downloads, uploads, JS interop calls, Web Locks, Picture-in-Picture). It vetoes the pause entirely while focused text inputs with Blazor `@bind` bindings are edited or audio/video is playing. -For elements without Blazor bindings (for example, ``, WebRTC connections, or custom elements), the app is responsible for handling state. Use `onPauseRequested` in the [Blazor startup configuration](xref:blazor/fundamentals/startup): +For elements without Blazor bindings (for example, ``, WebRTC connections, or custom elements), the app is responsible for handling state. Register a circuit handler with an `onCircuitPausing` callback in the [Blazor startup configuration](xref:blazor/fundamentals/startup): ```razor ``` > [!NOTE] -> The `` element can't have its value restored after pause/resume due to browser security restrictions. Using `[PersistentState]` on a property bound to a file input causes an `InvalidStateError` that crashes the circuit. Instead, capture the file name in a separate property: +> The `` element can't have its value restored after pause/resume due to browser security restrictions. Using `[PersistentState]` on a property bound to a file input causes an `InvalidStateError` that crashes the circuit. Instead, capture the file name in a separate property. Because the browser doesn't expose the file name through the native `change` event, read it with a small JS interop helper: > > ```razor -> +> @inject IJSRuntime JS +> +> > @SelectedFileName > > @code { > [PersistentState(AllowUpdates = true)] > public string? SelectedFileName { get; set; } > -> private void HandleFileSelected(InputFileChangeEventArgs e) +> private async Task HandleFileSelected(ChangeEventArgs e) > { -> SelectedFileName = e.File.Name; +> SelectedFileName = await JS.InvokeAsync("getFileName", "persist-upload"); > } > } > ``` +> +> ```javascript +> window.getFileName = (id) => document.getElementById(id)?.files?.[0]?.name ?? ''; +> ``` :::moniker-end From 7e6f8a17a454645bf0b39d77efb5a9b2b7412117 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Tue, 14 Jul 2026 11:33:28 +0200 Subject: [PATCH 4/4] Apply @guardrex's feedback. --- aspnetcore/blazor/state-management/server.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/state-management/server.md b/aspnetcore/blazor/state-management/server.md index 129380e30c50..cbcfcd10968c 100644 --- a/aspnetcore/blazor/state-management/server.md +++ b/aspnetcore/blazor/state-management/server.md @@ -187,6 +187,8 @@ window.addEventListener('visibilitychange', () => { ## Automatic circuit pause on tab inactivity + + The framework can optionally pause a circuit when the browser tab becomes hidden, freeing server memory and SignalR connections held by inactive users. Auto-pause is provided by the `Microsoft.AspNetCore.Components.Server.AutoPause` package. After adding a package reference, enable the feature by calling `AddAutoPause` when the app's root component is mapped: ```csharp @@ -199,17 +201,17 @@ app.MapRazorComponents() * `Enabled`: Whether auto-pause is enabled. Defaults to `true`, so calling `AddAutoPause` is what enables the feature. * `HiddenDelay`: The delay after the tab becomes hidden before the circuit pauses. Defaults to two minutes (`TimeSpan.FromMinutes(2)`) and must be greater than `TimeSpan.Zero`. -After the tab is hidden for `HiddenDelay`, the circuit pauses. If the user returns before the delay elapses, the pause doesn't happen. +After the tab is hidden for `HiddenDelay`, the circuit pauses. If the user returns before the delay elapses, the pause doesn't occur. > [!NOTE] > Auto-pause triggers on the [Page Visibility API](https://developer.mozilla.org/docs/Web/API/Page_Visibility_API) `visibilitychange` event, whose meaning differs by platform: > -> * On desktop, the tab becomes hidden when the user switches tabs or minimizes the window. The pause timer runs reliably and the circuit pauses gracefully after the delay. +> * On desktop, the tab becomes hidden when the user switches tabs or minimizes the window. The pause timer runs reliably, and the circuit pauses gracefully after the delay. > * On mobile, the page also becomes hidden when the *whole app* is backgrounded (switching apps, returning to the home screen, or locking the screen), not just when switching browser tabs. > -> On mobile, the operating system suspends the page's JavaScript shortly after the app is backgrounded (within seconds on Android, up to about 30 seconds on iOS). If `HiddenDelay` is longer than that window, the pause timer never fires and the circuit is dropped by the OS-initiated disconnect instead of pausing gracefully. The session is still preserved through the normal reconnection and [circuit state persistence](#circuit-state-persistence) path, but the client-side veto and deferral logic doesn't run. For this reason, graceful auto-pause isn't guaranteed and isn't a supported scenario on mobile when the app is backgrounded. +> On mobile, the operating system suspends the page's JavaScript shortly after the app is backgrounded (within seconds on Android, up to about 30 seconds on iOS). If `HiddenDelay` is longer than that window, the pause timer never fires, and the circuit is dropped by the OS-initiated disconnect instead of pausing gracefully. The session is still preserved through the normal reconnection and [circuit state persistence](#circuit-state-persistence) path, but the client-side veto and deferral logic doesn't run. For this reason, graceful auto-pause isn't guaranteed and isn't a supported scenario on mobile when the app is backgrounded. -The framework defers the pause while circuit-owned work is in progress (downloads, uploads, JS interop calls, Web Locks, Picture-in-Picture). It vetoes the pause entirely while focused text inputs with Blazor `@bind` bindings are edited or audio/video is playing. +The framework defers the pause while circuit-owned work is in progress (downloads, uploads, JS interop calls, Web Locks, Picture-in-Picture). It vetoes the pause entirely while focused text `` elements with Blazor `@bind` bindings are edited or audio/video is playing. For elements without Blazor bindings (for example, ``, WebRTC connections, or custom elements), the app is responsible for handling state. Register a circuit handler with an `onCircuitPausing` callback in the [Blazor startup configuration](xref:blazor/fundamentals/startup): @@ -243,7 +245,7 @@ For elements without Blazor bindings (for example, ``, WebRTC connection ``` > [!NOTE] -> The `` element can't have its value restored after pause/resume due to browser security restrictions. Using `[PersistentState]` on a property bound to a file input causes an `InvalidStateError` that crashes the circuit. Instead, capture the file name in a separate property. Because the browser doesn't expose the file name through the native `change` event, read it with a small JS interop helper: +> The `` element can't have its value restored after pause/resume due to browser security restrictions. Using `[PersistentState]` on a property bound to a file `` element causes an `InvalidStateError` that crashes the circuit. Instead, capture the file name in a separate property. Because the browser doesn't expose the file name through the native `change` event, read it with a small JS interop helper: > > ```razor > @inject IJSRuntime JS