【问题标题】:Blazor server-side Fluxor Dispatch Action on Browser tab closeBlazor 服务器端 Fluxor Dispatch Action on Browser 选项卡关闭
【发布时间】:2021-01-16 14:13:42
【问题描述】:

第一个 SO 帖子,所以如果我的问题没有充分放在一起,请告诉我!

用例: 用户打开浏览器,并按下“在设备上付款”按钮。我调度了一个 PayOnDevice 操作,它将 UI 更新为加载状态。我有一个 HandlePayOnDevice [Effect] 方法,该方法会执行该操作并异步启动设备以进行付款。当用户将卡插入设备时,支付成功/失败,异步方法解析并将 UI 更新为成功/失败。但是,如果浏览器在异步方法解析之前关闭,我想让设备完成并告诉另一个服务设备支付成功或失败。

问题: 我可以通过重写 FluxorComponent 的虚拟 Dispose(bool disposing) 方法来调度操作“BrowserClosed”,从而在技术上做到这一点。然后当原始设备异步方法解决时,我可以检查 IState 以查看浏览器是否关闭,以了解是否更新 UI 或使用结果更新其他系统。这是重写 Dispose 方法:

    protected override void Dispose(bool disposing)
    {
        Dispatcher.Dispatch(new BrowserClosedAction());
        base.Dispose(disposing);

    }

在此 dispose 方法中调用 Dispatch 的问题是渲染逻辑中断,因为组件已被释放,因此引发错误(但 IState 仍在更新,因此我的 Effect 方法仍然可以辨别是更新 UI 还是更新不同的服务):

Unhandled exception in circuit '2y97VfGYbGWD9xJIhtsbLOfj9PsQChpDlqBrGQdVXTQ'.
System.ObjectDisposedException: Cannot process pending renders after the renderer has been disposed.
Object name: 'Renderer'.
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContextDispatcher.InvokeAsync(Action workItem)
   at Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync(Action workItem)
   at Fluxor.Blazor.Web.Components.FluxorComponent.<OnInitialized>b__8_0(IState _)
   at Fluxor.StateSubscriber.<>c__DisplayClass2_1.<Subscribe>b__1(Object s, EventArgs a)
   at Fluxor.Feature`1.TriggerStateChangedCallbacks(TState newState)   at Fluxor.Feature`1.set_State(TState value)
   at Fluxor.Store.DequeueActions()
   at Fluxor.Store.Dispatch(Object action)
   at MyComponent.Dispose(Boolean disposing) in C:\MyComponentPath\MyComponent.razor:line XXX
   at Fluxor.Blazor.Web.Components.FluxorComponent.Dispose()        
   at Microsoft.AspNetCore.Components.Rendering.ComponentState.Dispose()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.Dispose(Boolean disposing)

从技术上讲,即使抛出了这个错误,我仍然可以做我需要做的事情,但我认为我不应该因为这个错误而走这条路(我希望这是可以避免的!)并且我想知道是否有另一种方法来实现这一点。我认为可能有一种方法可以创建 Scoped CircuitHandler 来调度动作,但我不知道如何将 IState/Dispatcher 注入到该电路处理程序中。此电路处理程序失败:

public class BrowserClosedHandler : CircuitHandler
{
    BrowserClosedHandler(IState<S.AppState> appState, IDispatcher dispatcher)
    {
        AppState = appState;
        Dispatcher = dispatcher;
    } 
    private IState<S.AppState> AppState;
    private IDispatcher Dispatcher;
    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        Dispatcher.Dispatch(new BrowserClosedAction());
        return Task.CompletedTask;
    }
}
...
// SERVICES SETUP
services.AddScoped<CircuitHandler, BrowserClosedHandler>();
services.AddFluxor(o => o
    .ScanAssemblies(typeof(Program).Assembly)
    .UseRouting()
);

这会导致启动时出错:

The application failed to start correctly
System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHandler Lifetime: Scoped ImplementationType: ECC.Startup+BrowserClosedHandler': A suitable constructor for type 'ECC.Startup+BrowserClosedHandler' could not be located. Ensure the type is concrete and services are registered for all parameters of a public constructor.)
 ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.AspNetCore.Components.Server.Circuits.CircuitHandler Lifetime: Scoped ImplementationType: ECC.Startup+BrowserClosedHandler': A suitable constructor for type 'ECC.Startup+BrowserClosedHandler' could not be located. Ensure the type 
is concrete and services are registered for all parameters of a public constructor.
 ---> System.InvalidOperationException: A suitable constructor for type 'ECC.Startup+BrowserClosedHandler' could not be located. Ensure 
the type is concrete and services are registered for all parameters 
of a public constructor.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)        
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)        
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.ValidateService(ServiceDescriptor descriptor)        
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)  
   --- End of inner exception stack trace ---
   at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(IEnumerable`1 serviceDescriptors, ServiceProviderOptions options)  
   at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options)
   at Microsoft.Extensions.DependencyInjection.DefaultServiceProviderFactory.CreateServiceProvider(IServiceCollection containerBuilder) 
   at Microsoft.Extensions.Hosting.Internal.ServiceFactoryAdapter`1.CreateServiceProvider(Object containerBuilder)
   at Microsoft.Extensions.Hosting.HostBuilder.CreateServiceProvider()
   at Microsoft.Extensions.Hosting.HostBuilder.Build()
   at ECC.Program.Main(String[] args) in C:\SomePath\Program.cs:line XXX

任何帮助将不胜感激!

【问题讨论】:

  • 所以只是盲目地看(而不是测试),难道它就像你的 BrowserClosedHandler 的构造函数不是公共的,但实际上是内部的一样简单吗?
  • 您是否尝试过在之后而不是之前调用 base.Dispose?如果这不起作用,你可以给我发邮件给我一个复制链接,这样我就可以试试了吗?我没有看到您描述的行为 - mrpmorris@gmail.com
  • @BenSampica 就是这样!非常感谢,这对您有很大帮助。
  • @PeterMorris 感谢您的评论!我尝试了你的建议,base.Dispose 的放置没有影响错误。您是否希望我通过电子邮件发送一个 repo 来查看这种情况?
  • 是的,请给我一些东西

标签: c# blazor blazor-server-side fluxor


【解决方案1】:

当您使用 FluxorComponent 作为基础时,它将扫描组件以查找所有 IState&lt;T&gt; 属性并订阅它们。每当此状态的值发生变化时,FluxorComponent 都会调用 StateHasChanged 来重新渲染组件。

当组件被 Disposed 时,FluxorComponent 将删除所有订阅以避免内存泄漏并尝试渲染已释放的组件。

你的情况是这样的

  1. Blazor 停用组件,使其无法再呈现
  2. 它调用Dispose
  3. 您被覆盖的 Dispose 调度一个提供新状态的操作
  4. IState&lt;T&gt; 订阅触发,StateHasChanged 针对无法呈现的组件执行。

解决方法是先调用base.Dispose(disposing),以便FluxorComponent 基类可以在您分派您的操作之前取消订阅该状态。

【讨论】:

  • 感谢您的关注!我通过电子邮件回复了您更新的存储库以重现该问题,但是任何可能正在阅读此 SO 线程的人,我看到的问题与嵌套的 FluxorComponent 有关,它也覆盖了 Dispose(disposing)。当嵌套组件执行此操作时,无论 Dispatch() 的位置如何,都会在 Parent 处理方法中抛出渲染错误。
  • @RichardMcDonald Fluxor 中还有一个错误,当您在正确的位置调用 base.Dispose 时会发生错误,但还会使用消耗子组件调度操作。我已经上传了 3.7.1-beta1 包供你测试(它是一个预发布版本,所以允许 VS 在搜索 Nuget 包更新时包含预发布版本)
  • 谢谢@PeterMorris!该测试版成功了。
猜你喜欢
  • 2019-10-01
  • 2013-12-10
  • 1970-01-01
  • 2020-02-18
  • 1970-01-01
  • 2021-05-28
  • 2020-05-15
  • 2020-07-08
  • 1970-01-01
相关资源
最近更新 更多