Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 15, 2025

Suppress JSDisconnectedException in IJSObjectReference.DisposeAsync

  • You've read the Contributor Guide and Code of Conduct.
  • You've included unit or integration tests for your change, where applicable.
  • You've included inline docs for your change, where applicable.
  • There's an open issue for the PR that you are making. If you'd like to propose a new feature or change, please open an issue to discuss the change or find an existing issue.

Catch and discard JSDisconnectedException during JS object disposal

Description

When a circuit disconnects (user navigates away, closes tab, or full-page reload), components disposing IJSObjectReference instances throw JSDisconnectedException. This forces developers to wrap disposal in try-catch blocks.

Changes:

  • Modified JSObjectReference.DisposeAsync() to catch and suppress JSDisconnectedException
  • Added tests verifying exception suppression on single and multiple dispose calls

Rationale:

If JS is disconnected, the JS-side object is already gone. Server-side disposal would fail regardless, so the exception provides no value and only creates noise.

Before:

public async ValueTask DisposeAsync()
{
    if (_jsModule is not null)
    {
        try
        {
            await _jsModule.DisposeAsync();
        }
        catch (JSDisconnectedException)
        {
            // Must manually catch in every component
        }
    }
}

After:

public async ValueTask DisposeAsync()
{
    if (_jsModule is not null)
    {
        await _jsModule.DisposeAsync(); // No try-catch needed
    }
}
Original prompt

This section details on the original issue you should resolve

<issue_title>Change IJSObjectReference.Dispose specifically so that it deals with catching and discarding JSDisconnectedException internally</issue_title>
<issue_description>I'm getting a warning and exception when trying to dispose a JavaScript module reference in a component's DisposeAsync method when the component is using the server interactive render mode.

Repro steps:

  • Install .NET 8 Preview 6
  • Clone https://github.com/danroth27/BestForYouRecipes and pull the dotnet8 branch
  • Make sure BestForYouRecipes/Pages/SubmitRecipe.razor is setup to use the server render mode for the recipe editor
  • Build and run the app
  • Browse to the Submit Recipe page
  • Browse back to the home page of the app

Expected result: No exception
Actual result: 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 is because the circuit has disconnected and is being disposed.
         at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(Int64 asyncHandle, String identifier, String argsJson, JSCallResultType resultType, Int64 targetInstanceId)
         at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, CancellationToken cancellationToken, Object[] args)
         at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
         at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
         at Microsoft.JSInterop.Implementation.JSObjectReference.DisposeAsync()
         at BestForYouRecipes.IngredientsListEditor.DisposeAsync() in C:\Users\daroth\Documents\GitHub\danroth27\BestForYouRecipes\BestForYouRecipes.Client\IngredientsListEditor.razor:line 136
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass83_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext()

fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111]
      Unhandled exception in circuit 'yAE5LTi2-XXk6e9JbmT_P1RmxksmTriTYTGkrzPgH2I'.
      Microsoft.JSInterop.JSDisconnectedException: JavaScript interop calls cannot be issued at this time. This is because the circuit has disconnected and is being disposed.
         at Microsoft.AspNetCore.Components.Server.Circuits.RemoteJSRuntime.BeginInvokeJS(Int64 asyncHandle, String identifier, String argsJson, JSCallResultType resultType, Int64 targetInstanceId)
         at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, CancellationToken cancellationToken, Object[] args)
         at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
         at Microsoft.JSInterop.JSRuntimeExtensions.InvokeVoidAsync(IJSRuntime jsRuntime, String identifier, Object[] args)
         at Microsoft.JSInterop.Implementation.JSObjectReference.DisposeAsync()
         at BestForYouRecipes.IngredientsListEditor.DisposeAsync() in C:\Users\daroth\Documents\GitHub\danroth27\BestForYouRecipes\BestForYouRecipes.Client\IngredientsListEditor.razor:line 136
         at Microsoft.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass83_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext()
```</issue_description>

<agent_instructions>One thing we could do is change IJSObjectReference.Dispose specifically so that it deals with catching and discarding JSDisconnectedException internally. It would be safe to do since if JS is disconnected, then there's no reason why the server should be concerned about disposing IJSObjectReference instances.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@SteveSandersonMS</author><body>
> Expected result: No exception
> Actual result: JSDisconnectedException

Why do you expect no exception? The component is trying to do a JS interop call after the browser connection has been closed, so it should throw this exception. You do need to catch it. This isn't specific to .NET 8 - the same thing would happen on .NET 7 if a user closes their browser tab or navigates to an external site.

I see that the usability here is quite poor though. One thing we could do is change `IJSObjectReference.Dispose` specifically so that it deals with catching and discarding `JSDisconnectedException` internally. It would be safe to do since if JS is disconnected, then there's no reason why the server should be concerned about disposing `IJSObjectReference` instances.</bod...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes dotnet/aspnetcore#49418

<!-- START COPILOT CODING AGENT TIPS -->
---

✨ Let Copilot coding agent [set things up for you](https://github.com/dotnet/aspnetcore/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

@dotnet-policy-service
Copy link
Contributor

Greetings! You've submitted a PR that modifies code that is shared with https://github.com/dotnet/runtime . Please make sure you synchronize this code with the changes in that repo!

Copilot AI changed the title [WIP] Fix IJSObjectReference.Dispose to handle JSDisconnectedException Suppress JSDisconnectedException in IJSObjectReference.DisposeAsync Dec 15, 2025
Copilot AI requested a review from javiercn December 15, 2025 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants