Blazor Web应用中,单例模式的AppData类如何在异步Initialize方法中访问依赖注入的服务
嘿,这个问题我之前在Blazor项目里刚好踩过坑,给你分享几个靠谱的实战方案:
方案一:在DI注册阶段完成异步初始化(推荐)
Blazor的DI容器支持异步注册单例,你可以直接在Program.cs里用AddSingleton的异步重载,把需要的服务从服务提供者里拿到后,传给Initialize方法。这种方式最省心,容器会自动保证初始化只执行一次,而且在首次请求AppData时自动完成异步操作。
代码示例:
// Program.cs中的注册代码 builder.Services.AddSingleton(async sp => { // 从服务容器中获取需要的IGraphServices实例 var graphService = sp.GetRequiredService<IGraphServices>(); // 实例化AppData var appData = new AppData(); // 调用异步初始化方法 await appData.Initialize(graphService); return appData; });
这里要注意:如果IGraphServices是Singleton生命周期的服务,直接获取完全没问题;但如果它是Scoped类型的,可不能这么直接拿——单例持有Scoped服务会导致服务实例被长期占用,引发内存泄漏或者上下文错误,这时候就得用下面的方案。
方案二:访问Scoped服务时,用作用域工厂创建临时作用域
如果你的IGraphServices是Scoped生命周期(比如需要绑定用户会话),那得通过IServiceScopeFactory创建一个临时作用域,在这个临时作用域里获取Scoped服务,用完就自动释放,避免和单例的长生命周期冲突。
代码示例:
builder.Services.AddSingleton(async sp => { // 获取作用域工厂实例 var scopeFactory = sp.GetRequiredService<IServiceScopeFactory>(); // 创建临时作用域 using var scope = scopeFactory.CreateScope(); // 从临时作用域中获取Scoped的IGraphServices var scopedGraphService = scope.ServiceProvider.GetRequiredService<IGraphServices>(); var appData = new AppData(); await appData.Initialize(scopedGraphService); return appData; });
这个方式的核心是临时作用域,用完后会自动回收里面的Scoped服务,不会出现生命周期不匹配的问题。
方案三:在应用启动阶段手动触发初始化
如果你不想把初始化逻辑绑定在DI注册里,也可以在应用启动的时候手动获取服务并执行初始化。比如在Program.cs的最后阶段:
代码示例:
var app = builder.Build(); // 从已构建的服务容器中拿到单例的AppData和需要的服务 var appData = app.Services.GetRequiredService<AppData>(); var graphService = app.Services.GetRequiredService<IGraphServices>(); // 执行异步初始化 await appData.Initialize(graphService); // 启动Blazor应用 await app.RunAsync();
不过这个方案有个小细节要注意:如果是Blazor Server,这会在服务器启动时执行初始化,而不是每个用户会话;如果是Blazor WebAssembly,要给初始化方法加好异常处理,别让网络请求失败导致应用启动卡死。另外,要确保AppData的Initialize方法是线程安全的,防止被多次调用。
最后再提个关键提醒:不管用哪个方案,都别让AppData长期持有Scoped服务的引用——单例的生命周期是整个应用运行期间,Scoped是每个请求/会话,持有会导致服务实例无法被正确回收,后续很容易出莫名其妙的问题!
内容来源于stack exchange

