Scenario contact: @dariatiurina
Scenario
This scenario validates that a reported bug is fixed. In Blazor Server apps using Interactive Server render mode, components frequently import JavaScript modules and dispose them in DisposeAsync. When the user closes the browser tab, navigates away, or the circuit otherwise disconnects, the dispose call used to throw JSDisconnectedException:
warn: Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer[100]
Unhandled exception rendering component: JavaScript interop calls cannot be issued at this time.
This is because the circuit has disconnected and is being disposed.
Microsoft.JSInterop.JSDisconnectedException: JavaScript interop calls cannot be issued at this time...
This forced every component that held an IJSObjectReference to wrap disposal in try/catch, even though the exception conveyed no actionable information: if JavaScript is disconnected, the JS-side object is already gone.
.NET 11 changes JSObjectReference.DisposeAsync to catch and suppress JSDisconnectedException internally. Components can now call await _jsModule.DisposeAsync() without wrapping it in a try-catch, and server logs will no longer contain these warnings during normal teardown.
Minimum build
.NET 11 RC1
Configurations to cover
The fix is in the server-side JSInterop runtime. WebAssembly does not have circuits and does not produce this exception.
Also exercise
Setup
A disconnected circuit is retained for three minutes by default, shorten it, so that the scenario does not appear to pass on both versions:
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents(o =>
{
o.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(5);
});
What to build
A Blazor Server component that imports a JavaScript module, uses it during its lifetime, and disposes it in DisposeAsync without a try-catch wrapper. The scenario is to disconnect the circuit (close the tab, navigate away) while the component is active and verify that no JSDisconnectedException appears in the server log.
Bracket the disposal with two log statements, one before the call and one after it. An absent exception is a weak signal on its own, because it also looks the same when the component was never disposed at all. With the bracketing, the second line appears only when the call really did complete.
Note that prerendering disposes the component as well, so every run produces two disposal cycles. The first has no circuit involved and completes cleanly on any version. Only the second one is meaningful, so do not read the result off the first pair of log lines.
Things to try
- Start with .NET 10 and observe the behavior. Then update to .NET 11 and observe again to confirm the fix.
- Create a component that imports a JavaScript module in
OnAfterRenderAsync and stores the reference:
private IJSObjectReference? _jsModule;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
_jsModule = await JS.InvokeAsync<IJSObjectReference>("import", "./myModule.js");
}
- Dispose it without try-catch:
public async ValueTask DisposeAsync()
{
if (_jsModule is not null)
await _jsModule.DisposeAsync();
}
- Run the app, navigate to the component, then close the browser tab or use browser back to an external site.
- Watch the server console or log output for warnings about
JSDisconnectedException.
Expected behavior
Disposing an IJSObjectReference after the circuit has disconnected completes without throwing and does not log a warning. The component cleanup finishes silently.
Must hold
- The server console shows no
JSDisconnectedException warning when the tab is closed while the component is active, and the log statement placed after the disposal call is reached.
- The component's
DisposeAsync does not require a try-catch around the JS module disposal.
- On .NET 10, the same code produces the
JSDisconnectedException warning in the server log.
Evidence to capture
The server log output during circuit disconnection from both versions, showing the bracketing log statements around the disposal call.
What to report
Report results using the format described in the validation testing manual. Include link to a repository with the test app.
Scenario contact: @dariatiurina
Scenario
This scenario validates that a reported bug is fixed. In Blazor Server apps using Interactive Server render mode, components frequently import JavaScript modules and dispose them in
DisposeAsync. When the user closes the browser tab, navigates away, or the circuit otherwise disconnects, the dispose call used to throwJSDisconnectedException:This forced every component that held an
IJSObjectReferenceto wrap disposal intry/catch, even though the exception conveyed no actionable information: if JavaScript is disconnected, the JS-side object is already gone..NET 11 changes
JSObjectReference.DisposeAsyncto catch and suppressJSDisconnectedExceptioninternally. Components can now callawait _jsModule.DisposeAsync()without wrapping it in a try-catch, and server logs will no longer contain these warnings during normal teardown.Minimum build
.NET 11 RC1
Configurations to cover
The fix is in the server-side JSInterop runtime. WebAssembly does not have circuits and does not produce this exception.
Also exercise
Setup
A disconnected circuit is retained for three minutes by default, shorten it, so that the scenario does not appear to pass on both versions:
What to build
A Blazor Server component that imports a JavaScript module, uses it during its lifetime, and disposes it in
DisposeAsyncwithout a try-catch wrapper. The scenario is to disconnect the circuit (close the tab, navigate away) while the component is active and verify that noJSDisconnectedExceptionappears in the server log.Bracket the disposal with two log statements, one before the call and one after it. An absent exception is a weak signal on its own, because it also looks the same when the component was never disposed at all. With the bracketing, the second line appears only when the call really did complete.
Note that prerendering disposes the component as well, so every run produces two disposal cycles. The first has no circuit involved and completes cleanly on any version. Only the second one is meaningful, so do not read the result off the first pair of log lines.
Things to try
OnAfterRenderAsyncand stores the reference:JSDisconnectedException.Expected behavior
Disposing an
IJSObjectReferenceafter the circuit has disconnected completes without throwing and does not log a warning. The component cleanup finishes silently.Must hold
JSDisconnectedExceptionwarning when the tab is closed while the component is active, and the log statement placed after the disposal call is reached.DisposeAsyncdoes not require a try-catch around the JS module disposal.JSDisconnectedExceptionwarning in the server log.Evidence to capture
The server log output during circuit disconnection from both versions, showing the bracketing log statements around the disposal call.
What to report
Report results using the format described in the validation testing manual. Include link to a repository with the test app.