“浏览器”选项卡上的Blazor服务器端Fluxor调度操作关闭

时间:2020-09-30 20:46:03

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

第一个SO帖子,所以如果我的问题没有适当地提出,请告诉我!

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

问题: 从技术上讲,我可以通过重写FluxorComponent的虚拟Dispose(布尔处置)方法来分派动作“ 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)

从技术上讲,即使抛出此错误,我仍然可以做我需要做的事情,但是由于该错误(我希望可以避免!),我不认为我应该走这条路线,并且想知道是否存在完成此任务的另一种方法。我认为可能有一种创建作用域电路处理程序来分派动作的方法,但是我不知道如何将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

任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:0)

使用FluxorComponent作为基础时,它将在组件中扫描所有IState<T>属性,并对其进行订阅。每当此状态的值更改时,FluxorComponent都会调用StateHasChanged来重新呈现该组件。

处置组件时,FluxorComponent将删除所有订阅,以避免内存泄漏并尝试呈现处置的组件。

您的情况就是这样

  1. Blazor会停用该组件,使其无法再渲染
  2. 它调用Dispose
  3. 您覆盖的Dispose调度了一个赋予新状态的动作
  4. 触发对IState<T>的订阅,并针对无法呈现的组件执行StateHasChanged。

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