diff --git a/aspnetcore/blazor/state-management/server.md b/aspnetcore/blazor/state-management/server.md index e89e31547121..bfd5e70a2a4e 100644 --- a/aspnetcore/blazor/state-management/server.md +++ b/aspnetcore/blazor/state-management/server.md @@ -197,16 +197,16 @@ This feature is useful in the following scenarios: `Circuit.RequestCircuitPauseAsync(CancellationToken)` is used to request that the connected client begin the graceful circuit-pause flow. The `CancellationToken` cancels the request before it is accepted by the framework. The method returns `true` if the request was accepted and the client was asked to begin pausing. - When a server-side Blazor application shuts down (for example, during deployment), connected clients lose their SignalR connection. The approach in this section: * Detects shutdown before the server closes connections. -* Triggers a pause on connected circuits. +* Triggers a pause on connected circuits via `Microsoft.AspNetCore.Components.Server.Circuits.Circuit.RequestCircuitPauseAsync`. * Preserves state using [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute) on component properties. In the following example implementation, the following code files are placed in a `Shutdown` folder at the root of the app: @@ -226,18 +226,21 @@ public class ShutdownCircuitOptions } ``` +Using the following approach, the fact that the code sends the `RequestCircuitPauseAsync` asynchronously doesn't mean that upon returning the value that the client is already paused. It's only a request to pause the client, which the client can defer. That's why the code includes the (`_shutdownTcs`), which is set when there aren't any circuits connected (all of them are successfully shut down). In case a client requests a deferral longer than the server allows, longer than `ShutdownTimeout`, the client doesn't persist state and experiences a normal connection loss. Other clients that don't defer the pause request have their connections re-established after the app goes back online with state persisted. + `Shutdown/CircuitShutdownService.cs`: ```csharp using System.Collections.Concurrent; +using Microsoft.AspNetCore.Components.Server.Circuits; using Microsoft.Extensions.Options; namespace PauseResumeOnShutdown.Shutdown; public class CircuitShutdownService { - private readonly ConcurrentDictionary - _handlers = new(); + private readonly ConcurrentDictionary + _circuits = new(); private readonly ShutdownCircuitOptions _options; private bool _isShuttingDown; private TaskCompletionSource _shutdownTcs = new(); @@ -253,29 +256,30 @@ public class CircuitShutdownService { _isShuttingDown = true; - if (_handlers.IsEmpty) + if (_circuits.IsEmpty) { return; } - foreach (var handler in _handlers.Values) - { - handler.Pause(); - } + var pauseTasks = _circuits.Values + .Select(c => c.RequestCircuitPauseAsync().AsTask()) + .Append(_shutdownTcs.Task); + + Task.WhenAll(pauseTasks).Wait(_options.ShutdownTimeout); _shutdownTcs.Task.Wait(_options.ShutdownTimeout); } - public void Register(string circuitId, ShutdownCircuitHandler handler) + public void Register(string circuitId, Circuit circuit) { - _handlers.TryAdd(circuitId, handler); + _circuits.TryAdd(circuitId, circuit); } public void Unregister(string circuitId) { - _handlers.TryRemove(circuitId, out _); + _circuits.TryRemove(circuitId, out _); - if (_isShuttingDown && _handlers.IsEmpty) + if (_isShuttingDown && _circuits.IsEmpty) { _shutdownTcs.TrySetResult(); } @@ -287,18 +291,16 @@ public class CircuitShutdownService ```csharp using Microsoft.AspNetCore.Components.Server.Circuits; -using Microsoft.JSInterop; namespace PauseResumeOnShutdown.Shutdown; -public class ShutdownCircuitHandler( - CircuitShutdownService shutdownService, - IJSRuntime jsRuntime) : CircuitHandler +public class ShutdownCircuitHandler(CircuitShutdownService shutdownService) + : CircuitHandler { public override Task OnConnectionUpAsync(Circuit circuit, CancellationToken cancellationToken) { - shutdownService.Register(circuit.Id, this); + shutdownService.Register(circuit.Id, circuit); return Task.CompletedTask; } @@ -310,22 +312,6 @@ public class ShutdownCircuitHandler( return Task.CompletedTask; } - - public void Pause() - { - _ = PauseCore(); - } - - private async Task PauseCore() - { - try - { - await jsRuntime.InvokeVoidAsync("remotePause"); - } - catch - { - } - } } ``` @@ -340,12 +326,12 @@ using PauseResumeOnShutdown.Shutdown; var builder = WebApplication.CreateBuilder(args); // Increase host shutdown timeout to allow time for pause operations -// Default value is 10 seconds +// Must be greater than `ShutdownTimeout` in `ShutdownCircuitOptions` +// otherwise the host terminates connections before circuits finish +// pausing builder.Host.ConfigureHostOptions(options => options.ShutdownTimeout = TimeSpan.FromSeconds(30)); -// Set circuit shutdown timeout to allow time for the host to restart -// Default value is 10 seconds per 'Shutdown/ShutdownCircuitOptions.cs' builder.Services.Configure(options => options.ShutdownTimeout = TimeSpan.FromSeconds(10)); @@ -364,19 +350,25 @@ var app = builder.Build(); // ... rest of pipeline ``` -In `App.razor` after the [server-side Blazor script reference](xref:blazor/project-structure#location-of-the-blazor-script), `window.remotePause` is called from the server to trigger pause and returns immediately to avoid a "`Cannot send data`" error when Blazor attempts to send the response back. +Optionally, to defer pause on the client until critical work completes (for example, an in-flight payment), configure the `onPauseRequested` callback in the [Blazor startup configuration](xref:blazor/fundamentals/startup). Place the following after the [server-side Blazor script reference](xref:blazor/project-structure#location-of-the-blazor-script): ```razor ``` -The `remotePause` function must not be `async` and must not return a value. If it returns a `Promise`, Blazor attempts to send the result back after the connection closes, which results in an error. +Without the `onPauseRequested` callback, the client pauses immediately when the server requests it. -In a component, use the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute) to persist component state across pause/resume. In the following `Counter` component example, the current count (`CurrentCount`) is preserved: +In a component, use the [`[PersistentState]` attribute](xref:Microsoft.AspNetCore.Components.PersistentStateAttribute) to persist component state across pause/resume. In the following `Counter` component example, the current count (`CurrentCount`) is preserved across server restarts using the preceding approach: ```razor @page "/counter" diff --git a/aspnetcore/host-and-deploy/health-checks.md b/aspnetcore/host-and-deploy/health-checks.md index cf0ca1afebbb..6cf80f93cbf8 100644 --- a/aspnetcore/host-and-deploy/health-checks.md +++ b/aspnetcore/host-and-deploy/health-checks.md @@ -183,7 +183,13 @@ The `DbContext` check confirms that the app can communicate with the database co * Use [Entity Framework (EF) Core](/ef/core/). * Include a package reference to the [`Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore`](https://www.nuget.org/packages/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore) NuGet package. - registers a health check for a . The `DbContext` is supplied to the method as the `TContext`. An overload is available to configure the failure status, tags, and a custom test query. + + +`AddDbContextCheck` registers a health check for a . The `DbContext` is supplied to the method as the `TContext`. An overload is available to configure the failure status, tags, and a custom test query. By default: diff --git a/aspnetcore/host-and-deploy/health-checks/includes/health-checks6-7.md b/aspnetcore/host-and-deploy/health-checks/includes/health-checks6-7.md index 80c6967f4ada..997da0046876 100644 --- a/aspnetcore/host-and-deploy/health-checks/includes/health-checks6-7.md +++ b/aspnetcore/host-and-deploy/health-checks/includes/health-checks6-7.md @@ -166,7 +166,13 @@ The `DbContext` check confirms that the app can communicate with the database co * Use [Entity Framework (EF) Core](/ef/core/). * Include a package reference to the [`Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore`](https://www.nuget.org/packages/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore) NuGet package. - registers a health check for a . The `DbContext` is supplied to the method as the `TContext`. An overload is available to configure the failure status, tags, and a custom test query. + + +`AddDbContextCheck` registers a health check for a . The `DbContext` is supplied to the method as the `TContext`. An overload is available to configure the failure status, tags, and a custom test query. By default: diff --git a/aspnetcore/release-notes/aspnetcore-11.md b/aspnetcore/release-notes/aspnetcore-11.md index 226c0253542f..bfff8f1bedfd 100644 --- a/aspnetcore/release-notes/aspnetcore-11.md +++ b/aspnetcore/release-notes/aspnetcore-11.md @@ -5,7 +5,7 @@ author: wadepickett description: Learn about the new features in ASP.NET Core in .NET 11. ms.author: wpickett ms.custom: mvc -ms.date: 05/12/2026 +ms.date: 05/13/2026 uid: aspnetcore-11 --- # What's new in ASP.NET Core in .NET 11 diff --git a/aspnetcore/release-notes/aspnetcore-11/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-11/includes/blazor.md index d1818c71cac2..61e6fb715ac4 100644 --- a/aspnetcore/release-notes/aspnetcore-11/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-11/includes/blazor.md @@ -176,7 +176,7 @@ This feature is useful in the following scenarios: * Instance draining. * App maintenance windows. -For more information, see . +For more information and an implementation example for server restarts, see .