16

I have a file drop zone implemented in one of the pages of my blazor application, which is handled using the javascript runtime interface. In order to avoid memory leaks, I have a javascript method, which removes all the event listeners. This is called from the dispose function as follows:

public ValueTask DisposeAsync() { ViewModel.PropertyChanged -= OnPropertyChangedHandler; GC.SuppressFinalize(this); return JSRuntime.InvokeVoidAsync("deInitializeDropZone"); } 

This works, however if I reload the page in my browser (F5, or reload button), I get the following exceptions:

fail: Microsoft.AspNetCore.Server.Kestrel[13] Connection id "0HMI59N5RRGP7", Request id "0HMI59N5RRGP7:0000000E": An unhandled exception was thrown by the application. System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method. 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.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass69_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Components.Rendering.HtmlRenderer.HandleException(Exception exception) at Microsoft.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass69_0.<Dispose>g__NotifyExceptions|2(List`1 exceptions) at Microsoft.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass69_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Components.RenderTree.Renderer.DisposeAsync() at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.<DisposeAsync>g__Await|22_0(Int32 i, ValueTask vt, List`1 toDispose) at Microsoft.AspNetCore.Http.Features.RequestServicesFeature.<DisposeAsync>g__Awaited|9_0(RequestServicesFeature servicesFeature, ValueTask vt) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.<FireOnCompleted>g__ProcessEvents|227_0(HttpProtocol protocol, Stack`1 events) 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.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass69_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext() fail: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost[111] Unhandled exception in circuit 'blLlOrw1UtfEHUoPBbO_N3peh7u3Or5Uk51p5RbR5xA'. 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.AspNetCore.Components.RenderTree.Renderer.<>c__DisplayClass69_0.<<Dispose>g__HandleAsyncExceptions|1>d.MoveNext() 

This will only happen on reload, if I switch to another page, the dispose function is called as well, but no exception. I'm not entirely sure what the reason for this problem is. Maybe it could relate to the initialization as well, which happens after first render:

protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { var authState = await AuthenticationStateTask; var user = authState.User; if (user.Identity.IsAuthenticated) { if (await ViewModel.LoadSelectedDatabase(DatasetID)) { await JSRuntime.InvokeVoidAsync("initializeDropZone"); } else { await JSRuntime.InvokeVoidAsync("alert", "The selected Dataset does not exist!"); NavigationManager.NavigateTo($"datasets"); } } } await base.OnAfterRenderAsync(firstRender); return; } 

Edit: Some more testing, exception is thrown before the await JSRuntime.InvokeVoidAsync("initializeDropZone"); is called after reload.

Edit#2: I also switched up the JS function:

public ValueTask DisposeAsync() { ViewModel.PropertyChanged -= OnPropertyChangedHandler; GC.SuppressFinalize(this); return JSRuntime.InvokeVoidAsync("console.log", "testilein"); //return JSRuntime.InvokeVoidAsync("deInitializeDropZone"); } 

This will result in the same errors on reload.

16
  • have you tryied to await the js call instead of returning its task Commented Jun 3, 2022 at 11:09
  • 1
    @spzvtbg thanks for the suggestion, I tried it and change the function to public async ValueTask DisposeAsync(), then inside awaited the JS with await JSRuntime.InvokeVoidAsync("deInitializeDropZone"); before returning. However, the issue still remains the same. Commented Jun 3, 2022 at 11:15
  • 1
    Your JS Interop calls should happen during OnAfterRender or OnAfterRenderAsync. This is when the components are in the browser where JS functionality can actually occur. Take a look at the lifecycle for Blazor components. Commented Jun 3, 2022 at 12:05
  • await JSRuntime.InvokeVoidAsync("console.log", "testilein").ConfigureAwait(false); - configure await false should prevent the task from running when the page is reloaded Commented Jun 3, 2022 at 12:17
  • @MoSlo I call the initialization during OnAfterRenderAsync, deinitialization is called during disposing of the page component. As far as I was concerned, the JS components (= the script tags) should be in the browser at those times - and they are if I switch to another page. However during reload, my page seems to be deleted and pre-rendered before disposed is called, which probably causes the issue. I'll have to look into it, but thanks for the tip pointing me into this direction. Commented Jun 3, 2022 at 12:23

6 Answers 6

13

Since it is impossible to call JavaScript when the SignalR connection is disconnected, I believe the recommended way is to wrap it in a try-catch and catch the JSDisconnectedException as shown below. Since event listeners stop existing after a page reload there are no memory leaks. Having said that, I agree this isn't very "elegant" though...

async ValueTask IAsyncDisposable.DisposeAsync() { try { ViewModel.PropertyChanged -= OnPropertyChangedHandler; GC.SuppressFinalize(this); await JSRuntime.InvokeVoidAsync("deInitializeDropZone"); } catch (JSDisconnectedException ex) { // Ignore } } 

Side note I made the implementation explicit since it is only accessed on an instance of the interface.

Sign up to request clarification or add additional context in comments.

1 Comment

I just don't get why you think SignalR connection is disconnected? Your solution solved my problem too but I don't understand why would SignalR be disconnected upon disposal of component?
4

Adding to Jesse's answer: I'm having the same issue as OP and currently looking for a proper solution. Seems there's a statement in the official docs, that confirm Jesse's answer:

Basically:

  • try-catch pattern
  • MutationObserver on client

JavaScript interop calls without a circuit

This section only applies to Blazor Server apps.

JavaScript (JS) interop calls can't be issued after a SignalR circuit is disconnected. Without a circuit during component disposal or at any other time that a circuit doesn't exist, the following method calls fail and log a message that the circuit is disconnected as a JSDisconnectedException:

JS interop method calls IJSRuntime.InvokeAsync JSRuntimeExtensions.InvokeAsync JSRuntimeExtensions.InvokeVoidAsync) Dispose/DisposeAsync calls on any IJSObjectReference.

In order to avoid logging JSDisconnectedException or to log custom information, catch the exception in a try-catch statement.

For the following component disposal example:

The component implements IAsyncDisposable. objInstance is an IJSObjectReference. JSDisconnectedException is caught and not logged. Optionally, you can log custom information in the catch statement at whatever log level you prefer. The following example doesn't log custom information because it assumes the developer doesn't care about when or where circuits are disconnected during component disposal.

C#

async ValueTask IAsyncDisposable.DisposeAsync() { try { if (objInstance is not null) { await objInstance.DisposeAsync(); } } catch (JSDisconnectedException) { } } 

If you must clean up your own JS objects or execute other JS code on the client after a circuit is lost, use the MutationObserver pattern in JS on the client.

Comments

0

I also had this same problem, and I discovered that it was caused by having services.AddBlazorFluentUI() in the startup. When this reference was removed, the error was no longer logged.

Comments

0

I encountered an issue in my Blazor Server app when I used @value="@context.aadiscount" instead of value="@context.aadiscount". The issue was resolved after removing the unnecessary '@' from the value tag.

Comments

0

you call script first time on the wrong place... You need to invoke script "init" here:

/// protected override async Task OnAfterRender(bool firstRender) {
if(firstRender) { // first possible call of javascript !!! } }

Comments

0

For cases when you are using InteractiveWebAssemblyRenderMode, you can disable prerendering so no code is :

@rendermode @(new InteractiveWebAssemblyRenderMode(prerender: false)) 

Then you can call JSInterop from OnInitialized(Async) with no issue.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.