Follow links when the interactive router is disconnected - #68562
Follow links when the interactive router is disconnected#68562oguzozshn wants to merge 1 commit into
Conversation
Link clicks were intercepted whenever an interactive router was registered, without considering whether that router could be reached. With the Blazor Server circuit down, performInternalNavigation dispatched to .NET over the circuit and failed with "Cannot send data if the connection is not in the 'Connected' State" - after preventDefault had already suppressed the browser's own navigation, leaving the user stuck on the page. Navigations now fall back to a full page load while the interactive router is unreachable. That follows the link and gives the app a chance to establish a new circuit. - NavigationUtils: add an optional connection checker, following the existing attachProgrammaticEnhancedNavigationHandler registration pattern. Rendering modes that never register one (i.e. WebAssembly) report as connected, so their behavior is unchanged. - NavigationManager: skip click interception without calling preventDefault, and return 'serverside-fullpageload' from currentPageLoadMechanism so programmatic Blazor.navigateTo is covered too. - CircuitManager: expose isConnected(). - Boot.Server.Common: register the checker. It reads the module-level circuit, so it keeps reporting the current one after a reconnection replaces the instance. Back/forward navigation (onPopState) hits the same interop call while disconnected and is intentionally left out of this change. Fixes dotnet#9964
|
Thanks for your PR, @oguzozshn. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
|
@dotnet-policy-service agree |
|
I looked into the failing checks — as far as I can tell none of them are caused by this change, but flagging what I found so a reviewer doesn't have to repeat the work. Blazor E2E tests on Linux (Mono) — 2 failures out of 2535:
Build Test: Windows local development validation — a file lock during static web asset compression: Build Monitor Helix Jobs — work item Both of those last two also occur on #68558, which only touches For what it's worth on the substance: none of the failing tests exercise navigation, and this change is a no-op while the circuit is connected — Happy to rebase if that would help rerun against a greener baseline. |
Fall back to a full page load when the interactive router is disconnected.
Description
Link clicks are intercepted whenever an interactive router is registered, without considering
whether that router can actually be reached:
With the Blazor Server circuit down,
performInternalNavigationends innotifyLocationChanged,which dispatches to .NET over the circuit and fails with
Cannot send data if the connection is not in the 'Connected' State. BecausehandleClickForNavigationInterceptionhas already calledpreventDefault(), the browser's ownnavigation is suppressed too, so the user is left on the page with nothing but a console error.
This change lets navigations fall back to a full page load while the interactive router is
unreachable, which follows the link and gives the app a chance to establish a new circuit. The
mechanism for that already exists —
currentPageLoadMechanism()can return'serverside-fullpageload', andnavigateToCoreroutes that toperformExternalNavigation— sothis mostly widens the condition that selects it.
Changes
NavigationUtils— adds an optional connection checker, following the same registrationpattern as
attachProgrammaticEnhancedNavigationHandler. Rendering modes that never register one(i.e. WebAssembly) report as connected, so their behavior is unchanged.
NavigationManager— skips click interception without callingpreventDefault(), so thebrowser performs the navigation itself; and returns
'serverside-fullpageload'fromcurrentPageLoadMechanism()so programmaticBlazor.navigateTois covered too.CircuitManager— exposesisConnected(), the public form of aHubConnectionStatecheckalready made in four places in the class.
Boot.Server.Common— registers the checker. It reads the module-levelcircuit, so it keepsreporting the current one after a reconnection replaces the instance.
38 lines of product code across 4 files. No public API change.
Design question
A full page load discards circuit state, which is what the reconnection UI exists to avoid. My
reasoning for accepting that here: this path is only reached after the user clicked a link to
another page, so the current page's state is being left behind either way — and today the
alternative isn't "state is preserved", it's a swallowed navigation plus an unhandled error.
If you'd rather this were opt-in, or gated on the reconnection attempts having been exhausted
rather than on the connection simply not being
Connectedright now, I'm happy to rework it.Scope
Deliberately limited to link clicks and programmatic navigation. Back/forward (
onPopState) reachesthe same interop call while disconnected, but the right behavior there is less obvious, so I left it
for a separate change.
Testing
src/Components/Web.JS/test/NavigationManager.test.ts, covering both directions: clicks areintercepted while connected, and left to the browser while disconnected. Reverting the fix fails
only the disconnected case.
Web.JSsuite passes (12 suites, 251 tests).npm run build:debugsucceeds for all four bundles, includingBoot.WebAssembly.mainbaseline for the touched files.Not yet covered: an E2E test, and manual verification against a real disconnected circuit.
Fixes #9964