依赖注入中的 Blazor(服务器)作用域对象创建多个实例

INN*_*VTV 4 .net c# asp.net-core blazor blazor-server-side

为了演示目的,假设我有一个名为 StateManager 的类:

public class StateManager
{
    public StateManager()
    {
        IsRunning = false;
    }

    public void Initialize()
    {
        Id = Guid.NewGuid().ToString();
        IsRunning = true;
        KeepSession();
    }

    public void Dispose()
    {
        Id = null;
        IsRunning = false;
    }

    public string Id { get; private set; }
    public bool IsRunning { get; private set; }

    private async void KeepSession()
    {
        while(IsRunning)
        {
            Console.WriteLine($"{Id} checking in...");
            await Task.Delay(5000); 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它有一个在启动后运行的方法,每 5 秒将其 Id 写入控制台。

在我的 Startup 类中,我将其添加为范围服务:

services.AddScoped<StateManager>();
Run Code Online (Sandbox Code Playgroud)

也许我使用了错误的位置,但在我的MainLayout.razor文件中,我在OnInitializedAsync()上初始化它

@inject Models.StateManager StateManager
...
@code{
    protected override async Task OnInitializedAsync()
    {
        StateManager.Initialize();
    }
}
Run Code Online (Sandbox Code Playgroud)

在渲染第一页后运行应用程序时,控制台输出显示有 2 个实例正在运行:

bcf76a96-e343-4186-bda8-f7622f18fb27 正在检查...

e5c9824b-8c93-45e7-a5c3-6498b19ed647 正在检查...

如果我在对象上运行Dispose() ,它将结束其中一个实例上的KeepSession() while 循环,但另一个实例继续运行。如果我运行Initialize() ,每次运行Initialize()时都会出现一个新实例时都会生成新实例,并且它们都使用其唯一的 id 写入控制台。我可以无限制地创造任意数量的作品。

我认为将 Scoped<> 服务注入 DI 可以保证每个电路都有该对象的单个实例吗?我还尝试在OnAfterRender()中初始化覆盖中进行初始化,以防预渲染过程创建双实例(尽管这并不能解释为什么我可以在注入服务的页面中创建如此多的实例)。

是不是有什么我处理不当的地方?除了 MainLayout 之外,还有更好的位置来初始化 StateManager 吗?

itm*_*nus 12

我还尝试在 OnAfterRender() 覆盖中进行初始化,以防预渲染过程创建双实例

这是由预渲染引起的并且StateManager未处理。

但你无法通过将初始化放在OnAfterRender(). 一个简单的方法是使用RenderMode.Server替代。

<应用程序>
     @(等待 Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered)) 
    @(等待 Html.RenderComponentAsync<App>(RenderMode.Server))
</应用程序>

由于您StateManager需要有关 的知识StateManagerEx,所以我们首先以虚拟人StateManagerEx为例,这比您的场景更容易:

public class StateManagerEx
{
    public StateManagerEx()
    {
        this.Id = Guid.NewGuid().ToString();
    }
    public string Id { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

Layout当您在模式下渲染它时RenderMode.Server

<p> @StateManagerEx.Id </p>
Run Code Online (Sandbox Code Playgroud)

您只会获得一次 ID。然而,如果你在RenderMode.ServerPrerendered模式下渲染它,你会发现:

  1. 当浏览器向服务器发送请求时(但在 Blazor 连接建立之前),服务器会预渲染应用程序并返回 HTTP 响应。这是第一次StateManagerEx创建。
  2. 然后在Blazor建立连接后,StateManagerEx创建另一个连接。

我创建了一个屏幕录制并将每帧的持续时间增加了+100ms,您可以看到它的行为与我们上面描述的完全相同(Id 发生了变化):

在此输入图像描述

这同样适用于StateManager. 当您在 模式下渲染时ServerPrerendered,将会有两个StateManager,一个是在 Blazor 连接建立之前创建的,另一个驻留在电路中。所以您会看到两个实例正在运行。

如果我运行 Initialize() ,则会出现一个新实例,每次运行 Initialize() 时都会生成新实例,并且它们都使用其唯一的 id 写入控制台。

每当您运行时,都会创建Initialize()一个新的。Guid但是,StateManager实例保持不变(而被StateManager.Id更改Initialize())。

是不是有什么我处理不当的地方?

StateManager 没有实现IDisposable. 如果我按如下方式更改班级:

public class StateManager : IDisposable
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

即使我渲染Appin模式,每个连接同时ServerPrerendered只有一个:91238a28-9332-4860-b466-a30f8afa5173 checking in...

在此输入图像描述