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.
public async ValueTask DisposeAsync(), then inside awaited the JS withawait JSRuntime.InvokeVoidAsync("deInitializeDropZone");before returning. However, the issue still remains the same.OnAfterRenderorOnAfterRenderAsync. This is when the components are in the browser where JS functionality can actually occur. Take a look at the lifecycle for Blazor components.await JSRuntime.InvokeVoidAsync("console.log", "testilein").ConfigureAwait(false);- configure await false should prevent the task from running when the page is reloadedOnAfterRenderAsync, 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.