c# - 依赖注入中的 Blazor(服务器)范围对象创建多个实例
问题描述
出于演示目的,假设我有一个名为 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);
}
}
}
它有一个在启动后运行的方法,该方法每 5 秒将其 Id 写入控制台。
在我的 Startup 类中,我将其添加为 Scoped 服务:
services.AddScoped<StateManager>();
也许我使用了错误的位置,但在我的MainLayout.razor文件中,我在OnInitializedAsync()上对其进行了初始化
@inject Models.StateManager StateManager
...
@code{
protected override async Task OnInitializedAsync()
{
StateManager.Initialize();
}
}
在呈现第一页后运行应用程序时,控制台输出显示有 2 个实例正在运行:
bcf76a96-e343-4186-bda8-f7622f18fb27 正在登记...
e5c9824b-8c93-45e7-a5c3-6498b19ed647 正在登记...
如果我在对象上运行Dispose() ,它会在其中一个实例上结束KeepSession() while 循环,但另一个实例继续运行。如果我运行Initialize()会出现一个新实例,并且每次运行Initialize()都会生成新实例,并且它们都使用其唯一 ID 写入控制台。我可以无限制地创建任意数量的内容。
我认为将 Scoped<> 服务注入 DI 可以保证每个电路有一个该对象的单个实例?我还尝试在OnAfterRender()覆盖中进行初始化,以防预渲染过程创建双实例(尽管这不能解释为什么我可以在注入服务的页面中创建这么多实例)。
有什么我没有正确处理的吗?除了 MainLayout 之外,还有更好的位置来初始化 StateManager 吗?
解决方案
我还尝试在 OnAfterRender() 覆盖中进行初始化,以防预渲染过程创建双实例
它是由预渲染和未处理引起的StateManager
。
但是你不能通过将初始化放在OnAfterRender()
. 一个简单的方法是改用RenderMode.Server
。
<app>@(await Html.RenderComponentAsync<App>(RenderMode.ServerPrerendered))@(await Html.RenderComponentAsync<App>(RenderMode.Server)) </app>
由于您StateManager
需要了解StateManagerEx
,所以我们首先以一个假人StateManagerEx
为例,这比您的场景更容易:
public class StateManagerEx
{
public StateManagerEx()
{
this.Id = Guid.NewGuid().ToString();
}
public string Id { get; private set; }
}
Layout
在模式下渲染它时RenderMode.Server
:
<p> @StateManagerEx.Id </p>
您只会获得一次 ID。但是,如果您在RenderMode.ServerPrerendered
模式下渲染它,您会发现:
- 当浏览器向服务器发送请求时(但在 Blazor 连接建立之前),服务器会预先呈现应用程序并返回 HTTP 响应。这是第一次
StateManagerEx
创建。 - 然后在
Blazor
建立连接后,创建另一个StateManagerEx
。
我创建了一个屏幕录制并将每帧的持续时间增加+100ms
,您可以看到它的行为与我们上面描述的完全相同(Id 发生了变化):
对于StateManager
. 在ServerPrerendered
模式下渲染时,将有两个StateManager
,一个在 Blazor 连接建立之前创建,另一个驻留在电路中。因此,您将看到两个实例正在运行。
如果我运行 Initialize() 会出现一个新实例,并且每次运行 Initialize() 都会生成新实例,并且它们都使用其唯一 ID 写入控制台。
每当您运行Initialize()
时,都会创建一个新Guid
的。但是,StateManager
实例保持不变( whileStateManager.Id
由 更改Initialize()
)。
有什么我没有正确处理的吗?
您StateManager
没有实现IDisposable
. 如果我改变类如下:
public class StateManager : IDisposable
{
...
}
即使我渲染App
in模式,每个连接同时ServerPrerendered
只有一个:91238a28-9332-4860-b466-a30f8afa5173 checking in...
推荐阅读
- python - 如何根据另一个元素访问字典的子元素?
- office-js - Excel JavaScript 自定义函数中的“ReferenceError:找不到变量:需要”
- laravel - 30 分钟后取下按钮。拉拉维尔
- oracle-apex - 共享组件 oracle apex 中的动态 where 子句
- java - 每次重定向到错误页面时登录我的spring boot项目
- r - 将 renderUI 对象从 flexdashboard 移动到闪亮的应用程序
- angular - NestJs:Angular Universal ReferenceError - KeyboardEvent 未定义
- dart - 将 double 转换为二进制表示
- bootstrap-4 - Bootstrap 将第二列放在手机上的第一列下方
- java - 在 Java 中练习长构造函数是否很好?