You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server:Singleton服务如何获取Scoped服务ProtectedSessionStorage的数据?

Blazor Server 中 Singleton 服务访问 Scoped 服务的解决方案

问题背景

我们在Blazor Server应用中碰到以下场景:

  • 用户登录认证通过后展示公司列表
  • 用户选中公司后,将CompanyDBName和CompanyUi存入ProtectedSessionStorage(Scoped生命周期,从登录到注销有效)
  • 有一个必须为Singleton的MainService,其DbConnectionString存储不含Initial Catalog的通用连接字符串,需要通过GetCompanyConnectionString方法添加CompanyDbName生成对应公司的连接字符串,供数据库工厂使用
  • 直接在MainService构造函数注入ProtectedSessionStorage会报错,因为Singleton服务不能依赖Scoped服务,生命周期不兼容

原错误代码:

public class MainService
{
    private readonly ProtectedSessionStorage _protectedSessionStorage;

    public string DbConnectionString{ get; set; } = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=;Integrated Security=True";

    // 构造函数注入Scoped服务会报错
    public MainService(ProtectedSessionStorage protectedSessionStorage)
    { 
        _protectedSessionStorage = protectedSessionStorage; 
    }
    
    async Task<string> GetCompanyConnectionString()
    {
        var companyDbName = await _protectedSessionStorage.GetAsync<string>("UserDb");
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DbConnectionString);
        builder.InitialCatalog = companyDbName;
        return builder.ToString();
    }
}

服务注册:

builder.Services.AddSingleton<MainService>();

解决方案

方案1:将CompanyDbName作为参数传入方法

这是最直接且符合依赖原则的方案——让调用方(通常是Scoped组件/服务)负责获取CompanyDbName,再传给MainService的方法。这样MainService无需依赖Scoped服务,保持Singleton的纯洁性。

修改后的MainService:

public class MainService
{
    public string DbConnectionString{ get; set; } = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=;Integrated Security=True";

    public MainService()
    { 
        // 无需注入Scoped服务
    }
    
    // 直接接收CompanyDbName参数
    string GetCompanyConnectionString(string companyDbName)
    {
        if(string.IsNullOrEmpty(companyDbName))
            throw new ArgumentNullException(nameof(companyDbName));
            
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DbConnectionString);
        builder.InitialCatalog = companyDbName;
        return builder.ToString();
    }
}

调用示例(在Blazor组件中):

@inject ProtectedSessionStorage ProtectedSessionStorage
@inject MainService MainService

@code {
    private async Task UseCompanyConnection()
    {
        var companyDbNameResult = await ProtectedSessionStorage.GetAsync<string>("UserDb");
        if(companyDbNameResult.Success)
        {
            var connectionString = MainService.GetCompanyConnectionString(companyDbNameResult.Value);
            // 传给数据库工厂使用
        }
    }
}

方案2:通过IServiceProvider按需创建Scoped作用域

如果必须在MainService内部获取CompanyDbName,可以注入IServiceProvider,在方法内创建Scoped作用域来获取ProtectedSessionStorage。注意必须在Blazor的组件上下文中调用此方法(确保当前有活跃的Scoped会话)。

修改后的MainService:

public class MainService
{
    private readonly IServiceProvider _serviceProvider;
    public string DbConnectionString{ get; set; } = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=;Integrated Security=True";

    // 注入IServiceProvider(Singleton生命周期,安全)
    public MainService(IServiceProvider serviceProvider)
    { 
        _serviceProvider = serviceProvider;
    }
    
    async Task<string> GetCompanyConnectionString()
    {
        // 创建Scoped作用域
        using var scope = _serviceProvider.CreateScope();
        var protectedSessionStorage = scope.ServiceProvider.GetRequiredService<ProtectedSessionStorage>();
        
        var companyDbNameResult = await protectedSessionStorage.GetAsync<string>("UserDb");
        if(!companyDbNameResult.Success || string.IsNullOrEmpty(companyDbNameResult.Value))
            throw new InvalidOperationException("未找到选中的公司数据库名称");
            
        SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DbConnectionString);
        builder.InitialCatalog = companyDbNameResult.Value;
        return builder.ToString();
    }
}

服务注册保持不变:

builder.Services.AddSingleton<MainService>();

注意事项

  • 方案1优先推荐:符合单一职责,避免Singleton依赖Scoped服务的生命周期问题,代码更清晰
  • 方案2需谨慎使用:必须确保调用方法时处于活跃的Blazor会话上下文(比如在组件事件、页面初始化中调用),如果在后台任务中调用,可能无法获取到正确的Session数据
  • 避免使用IHttpContextAccessor:Blazor Server中,HttpContext仅在初始页面加载时存在,后续SignalR交互中没有HttpContext,依赖它会导致不可靠的行为

内容的提问来源于stack exchange,提问作者Pedro Horta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 13:03:26