Blazor 服务器端应用程序在重新加载期间抛出 "System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. ..."

Blazor server side application throwing "System.InvalidOperationException: JavaScript interop calls cannot be issued at this time. ..." during reload

我在我的 Blazor 应用程序的一个页面中实现了一个文件放置区,它是使用 javascript 运行时接口处理的。为了避免内存泄漏,我有一个 javascript 方法,它会删除所有事件侦听器。这是从 dispose 函数中调用的,如下所示:

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

这有效,但是如果我在浏览器中重新加载页面(F5,或重新加载按钮),我会遇到以下异常:

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()

这只会在重新加载时发生,如果我切换到另一个页面,dispose 函数也会被调用,但也不例外。 我不完全确定这个问题的原因是什么。也许它也可能与初始化有关,这发生在第一次渲染之后:

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;
}

编辑:更多测试,在重新加载后调用 await JSRuntime.InvokeVoidAsync("initializeDropZone"); 之前抛出异常。

Edit#2: 我也开启了JS功能:

public ValueTask DisposeAsync()
{
    ViewModel.PropertyChanged -= OnPropertyChangedHandler;
    GC.SuppressFinalize(this);

    return JSRuntime.InvokeVoidAsync("console.log", "testilein");
    //return JSRuntime.InvokeVoidAsync("deInitializeDropZone");
}

这将导致重新加载时出现相同的错误。

由于SignalR连接断开时无法调用JavaScript,我相信推荐的方法是将其包装在try-catch中并捕获JSDisconnectedException,如下所示. 由于事件侦听器 没有内存泄漏。 话虽如此,我同意这不是很“优雅”……

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

旁注我明确了实现,因为它只能在接口实例上访问。