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
相关产品推荐
相关产品推荐

