为每个 Blazor Circuit 创建一个对象 (ViewModel)

Creating an object (ViewModel) once for each Blazor Circuit

我目前正在编写一个小型 Blazor Web UI。我正在尝试采用/实施 MVVM 模式。为此,我创建了两个组件库 classes。一种只处理 Blazor 生命周期方法(添加一些异常处理),另一种根据这些生命周期方法的执行处理 ViewModel 初始化。该组件还实现了 IDisposable 接口,当组件变得不可见时,Blazor 会自动调用该接口。

这里是我的 WebComponentBase class 和 ViewModelAwareComponent class 的代码片段,以粗略地给出关于模式的想法:


    public abstract class WebFpComponentBase : ComponentBase, IDisposable, IHtmlStyles
    {

         private const string DEFAULT_DIM_VALUE = "auto";

         [Inject]
         protected IExceptionUiHandler<ErrorRedirectViewModel> ExceptionUiHandler { get; set; }

         //fields and parameters omitted for brevity

         #region Blazor Component LifeCycle
         /// <summary>
         /// OnInitialized is called after OnInitialized, when the component has received all initial parameters. Place any asynchronous operations here,
         /// which require the component to re-render.
         /// </summary>
         /// <returns></returns>
         protected override async Task OnInitializedAsync()
         {
              try
              {
                  await base.OnInitializedAsync();
                  _logger.Info($"{nameof(OnInitializedAsync)} - method invoked in component of type ${this.GetType().FullName}");
                  await OnInitializedInternalAsync();
                  _logger.Info($"{nameof(OnInitializedAsync)} - method finished in component of type ${this.GetType().FullName}");
              } catch(Exception ex)
              {
                 //Exception, if any happend, is forwared using the IExceptionUiHandler in the AfterRenderAsync() method
                  _forwardException = ex;
                  _logger.Error($"{nameof(OnInitializedAsync)}: Catching and forwarding exception of type {_forwardException.GetType().FullName}");
              }
         }

         protected abstract Task OnInitializedInternalAsync();
         //other methods of the class omitted for brevity
    }

接下来是我的 ViewModelAwareComponent,它确实有一个 属性 包含 ViewModel 并通过实现 [BlazorLifecycleMethod] 内部抽象方法自动触发 ViewModel 初始化和取消初始化(关闭任何服务连接,重置任何值等) .

public abstract class ViewModelAwareComponent<TViewModel> : WebFpComponentBase where TViewModel : BaseViewModel
{
    private Logger _logger = LogManager.GetCurrentClassLogger();

    [Parameter]
    public virtual TViewModel ViewModel { get; set; }

    protected override async Task OnInitializedInternalAsync()
    {
        await ViewModel.InitializeAsync();
        ViewModel.PropertyChanged += this.FireComponentStateHasChanged;
    }

    public override async void Dispose()
    {
        base.Dispose();
        await ViewModel.DeactivateAsync();
    }

    protected virtual async void FireComponentStateHasChanged(object sender, PropertyChangedEventArgs e)
    {
        _logger.Trace($"FireComponentStateHasChanged: property {e.PropertyName} has changed!");
        await InvokeAsync(this.StateHasChanged);
    }

    protected override async Task OnParametersSetAsyncInternal()
    {
        if (ViewModel == null)
        {
            throw new ArgumentNullException($"{nameof(ViewModel)}", "Parameter must be supplied!");
        }
    }
}

BaseViewModel-Type 仅以典型方式实现 INotifyPropertyChanged。我确实有一个 "MainViewModel" 应该只为整个连接(Blazor Circuit)实例化一次。因此我在 Startup.cs 中通过 services.AddScoped() 添加了它。因为它没有绑定到任何特定组件,所以我将它注入到我的 MainLayout.razor 中,这是我编写的每个 Razor 组件的布局。

布局确实实现了 protected override async Task OnInitializedAsync(),如下所示:

protected override async Task OnInitializedAsync()
{
    MainViewModel.PropertyChanged += (obj, args) => InvokeAsync(StateHasChanged);
    await MainViewModel.InitializeAsync();
    //some other stuff happening afterwards
}

我现在的问题是,每次启动应用程序时都会进行两次初始化,而不是每次连接只进行一次。反初始化也是一样,因为组件的Dispose()也被调用了两次

调试时我注意到在我的两个现有页面(路由组件)之间切换时不会重新调用 OnInitializedAsync。它只在启动时被调用两次。

您对这种行为有什么建议吗?有没有更好的方法来实现我想要的 MainViewModel 行为?

此致,

倾斜

由 dani herrera 的评论回答:

Maybe is it about pre-render? See my answer here:

通过更改 @(await Html.RenderComponentAsync(RenderMode.ServerPrerendered)) @(await Html.RenderComponentAsync(RenderMode.Server)) 现在 MainLayout.razor 的 OnInitializedAsync() 只被调用一次。

我看到这已得到解答,但我们还通过编写一个小型 Blazor 电路处理程序然后将返回的电路 ID 映射到作用域会话服务来完成此操作。电路处理程序在创建新电路时提供事件(或 disconnected/reconnected)。

https://docs.microsoft.com/en-us/aspnet/core/blazor/advanced-scenarios?view=aspnetcore-5.0