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

Blazor Server端如何在Razor Pages外获取Identity User实现多租户

方案合理性评估

你当前采用的共享数据库+行级TenantId全局查询过滤器的多租户方案是Blazor Server场景下的主流合规实现,没有原则性问题,唯一可以优化的就是TenantId传递的重复逻辑,不用每次调用方法都手动传参。

问题1解答:能否注册ApplicationUser单例服务

不能这么做,原因如下:

  • 单例服务的生命周期是整个应用全局共享,Blazor Server是多用户应用,所有在线用户会共用同一个单例实例,会出现不同用户的租户信息串扰的严重安全问题。

正确实现方式:
注册Scoped生命周期的租户提供服务,Blazor Server的Scoped生命周期对应单个用户的连接会话(Circuit),单用户内所有请求共享同一个实例,完全符合你的需求,示例代码如下:

// 定义租户提供接口
public interface ITenantProvider
{
    Task<int> GetCurrentTenantIdAsync();
    Task<ApplicationUser> GetCurrentUserAsync();
}

// 实现类
public class TenantProvider : ITenantProvider
{
    private readonly AuthenticationStateProvider _authStateProvider;
    private readonly UserManager<ApplicationUser> _userManager;
    private ApplicationUser _cachedUser;
    private int? _cachedTenantId;

    public TenantProvider(AuthenticationStateProvider authStateProvider, UserManager<ApplicationUser> userManager)
    {
        _authStateProvider = authStateProvider;
        _userManager = userManager;
    }

    public async Task<ApplicationUser> GetCurrentUserAsync()
    {
        if (_cachedUser != null) return _cachedUser;
        var authState = await _authStateProvider.GetAuthenticationStateAsync();
        if (!authState.User.Identity.IsAuthenticated) return null;
        _cachedUser = await _userManager.FindByNameAsync(authState.User.Identity.Name);
        return _cachedUser;
    }

    public async Task<int> GetCurrentTenantIdAsync()
    {
        if (_cachedTenantId.HasValue) return _cachedTenantId.Value;
        var user = await GetCurrentUserAsync();
        _cachedTenantId = user?.TenantId ?? 0;
        return _cachedTenantId.Value;
    }
}

在Program.cs里注册:

builder.Services.AddScoped<ITenantProvider, TenantProvider>();

之后所有需要拿租户ID的类,直接注入ITenantProvider调用对应方法即可,不用重复写用户查询逻辑。

问题2解答:能否在普通C#类中获取ApplicationUser

可以实现,构造函数本身不支持异步操作,所以不要在构造函数里获取用户,把获取逻辑封装为异步方法,需要使用的时候调用即可,示例如下:

public class CaronteWebService
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private readonly ITenantProvider _tenantProvider;

    // 构造函数只做注入,不做异步查询
    public CaronteWebService(ITenantProvider tenantProvider, AuthenticationStateProvider authenticationStateProvider, UserManager<ApplicationUser> userManager)
    {
        _tenantProvider = tenantProvider;
        _authenticationStateProvider = authenticationStateProvider;
        _userManager = userManager;
    }

    // 需要用到用户的方法,异步获取即可
    public async Task<Employee[]> GetEmployeesAsync()
    {
        // 直接从租户服务拿ID,不用手动传参
        var tenantId = await _tenantProvider.GetCurrentTenantIdAsync();
        using var ctx = new ProgramDbContext(tenantId);
        return ctx.Employee.Select(d => new Employee
        {
            Id = d.Id,
            TenantId = d.TenantId,
            Name = d.Name
        }).ToArray();
    }
}

注意:如果是没有用户上下文的场景(比如后台定时任务、非HTTP触发的服务调用),AuthenticationStateProvider无法获取到用户信息,这种场景需要主动传入租户ID。

额外优化建议

你当前手动new DbContext的方式可以改成依赖注入注册,把TenantId自动注入到DbContext中,不用每次手动传参。

内容的提问来源于stack exchange,提问作者Gabriele Conti

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 04:57:07