提问人:athagiorgos 提问时间:11/16/2023 更新时间:11/16/2023 访问量:54
在 DbContext 中为多租户应用程序注入 userid
Injecting userid in DbContext for multi-tenant application
问:
我正在构建一个具有 .net 标识的多租户应用程序。我希望每个用户在登录时都能查看自己的数据。我有一个用户解析器服务,我在其中注入 httpContextAccessor 以尝试从 HttpContext 的声明主体注入用户 ID。
internal sealed class AppUserResolver : IAppUserResolver
{
private readonly IHttpContextAccessor _httpContextAccessor;
public AppUserResolver(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string GetCurrentAppUserId()
{
return _httpContextAccessor.HttpContext!.User
.FindFirstValue(ClaimTypes.NameIdentifier)!;
}
}
然后,我将服务注入到 DbContext 类中。
public class AppDbContext : IdentityDbContext<AppUser>
{
private readonly IAppUserResolver _appUserResolver;
public AppDbContext(DbContextOptions<AppDbContext> options, IAppUserResolver appUserResolver)
: base(options)
{
_appUserResolver = appUserResolver;
}
public string GetUserId()
{
return _appUserResolver.GetCurrentAppUserId();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new UserInfoConfiguration(GetUserId()));
modelBuilder.ApplyConfiguration(new EmailInfoConfiguration(GetUserId()));
modelBuilder.ApplyConfiguration(new SocialMediaInfoConfiguration(GetUserId()));
}
}
我怎么注意到当 DI 容器注入 DbContext 时声明不可用,因此我没有直接在 DbContext 构造函数中获取声明,而是创建了一个方法,仅在需要时获取它们。
但问题出在 OnModleCreating 方法上。此方法仅在应用启动且 EF Core 缓存配置时调用一次。但是在此方法中,我已将 HasQueryFilter 方法应用于某些实体,但是它被设置为空字符串,因为当用户尝试登录时,声明为 null,因此 dbContext 第一次初始化时,它使用 null 用户 ID 进行初始化,OnModelCreating 也是如此。
internal sealed class UserInfoConfiguration : IEntityTypeConfiguration<UserInfo>
{
private readonly string _appUserId;
public UserInfoConfiguration(string appUserId)
{
_appUserId = appUserId;
}
public void Configure(EntityTypeBuilder<UserInfo> builder)
{
builder.ToTable("UserInfo");
builder.HasKey(x => x.Id);
builder.HasOne<AppUser>(x => x.AppUser)
.WithOne(x => x.UserInfo)
.HasForeignKey<UserInfo>(x => x.AppUserId)
.IsRequired();
builder.HasQueryFilter(x => x.AppUserId == _appUserId);
}
}
登录后,声明主体可用于每个用户操作,但 OnModelCreating 已运行并缓存。因此,之后进行的查询是错误的,并且由于它们使用 mepty 字符串作为用户 ID 进行过滤,因此会带来 nonthing。关于如何解决这个问题的任何建议,或者我应该使用用户 ID 过滤任何地方?
我还在服务注册时注册了 HttpContextAccessor 和我的用户解析器服务
serviceCollection.AddHttpContextAccessor();
serviceCollection.AddScoped<IAppUserResolver, AppUserResolver>();
答:
实现依赖于 HttpContextAccessor 的自定义服务并将其注入 DbContext 似乎没有必要。在我看来,你似乎在试图重新发明轮子。为什么不简单地在控制器中使用属性呢?授权中间件将自动使用当前用户的声明填充此属性(如果使用 cookie authn 方案,则从 cookie 中读取)。User
您可以使用 方便地检索所有用户声明。
用于代码重用。您可以实现这样的帮助程序方法,您可以从控制器调用该方法:User.Identity.Claims
public static string GetCurrentUserName(ClaimsPrincipal user)
=>
!string.IsNullOrWhiteSpace(user?.Identity?.Name) && user.Identity.IsAuthenticated
? user.Identity.Name
: "";
此示例仅返回用户名,但您可以对其进行编辑以返回您愿意检索的确切声明。
注意:对于这种方法,您需要将相关属性注册为用户声明,以便它位于表中,这可确保信息将作为声明存储在 cookie 中。AspNetUserClaims
例如,下面介绍如何将声明分配给用户:
var someUser = userMgr.FindByNameAsync("JohnWick").Result;
userMgr.AddClaimsAsync(someUser, new Claim[]{
new Claim("Company", "Mercedes")
}).Result;
注意:这是声明方法,但你也可以选择使用角色(这些角色也几乎是声明tbh,只是灵活性稍差,并且你使用不同的标识方法)。如果您不想将此信息保留在 cookie 中,那么对于每个请求,您都必须从用户表的数据库中读取属性,这不是一种高性能的方法,因此我建议不要这样做。
评论
User
评论