为什么 AppTenant 仅在播种数据时为空?

Why is AppTenant null only while seeding data?

我目前正在和 Ben Fosters 打交道 Saaskit

我已经使用 AppTenantId 属性 扩展了 ApplicationUser 并创建了一个自定义 UserStore,它使用 AppTenant 来识别用户:

public class TenantEnabledUserStore : IUserStore<ApplicationUser>, IUserLoginStore<ApplicationUser>,
    IUserPasswordStore<ApplicationUser>, IUserSecurityStampStore<ApplicationUser>
{
    private bool _disposed;
    private AppTenant _tenant;
    private readonly ApplicationDbContext _context;

    public TenantEnabledUserStore(ApplicationDbContext context, AppTenant tenant)
    {
        _context = context;
        _tenant = tenant;
    }
    /*... implementation omitted for brevity*/
}

如果用户注册或登录,这可以正常工作。 AppTenant 设置正确。当在我的 Statup.Configure() 方法结束时调用 SeedData.Initialize(app.ApplicationServices); 时出现问题:

public static class SeedData
{
    public async static void Initialize(IServiceProvider provider)
    {
        using (var context = new ApplicationDbContext(
            provider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
        {
            var admin = new ApplicationUser
            {
                AppTenantId = 1,
                Email = "foo@bar.com",
                UserName = "Administrator",
                EmailConfirmed = true
            };

            if(!context.Users.Any(u => u.Email == admin.Email))
            {
                var userManager = provider.GetRequiredService<UserManager<ApplicationUser>>();
                await userManager.CreateAsync(admin, "Penis123#");
            }
            context.SaveChanges();
        }
    }
}

用户管理器正在调用自定义用户存储,但现在 AppTenant 为空。 当代码最终到达

public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
    return _context.Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.AppTenantId == _tenant.AppTenantId, cancellationToken);
}

我面临一个 System.InvalidoperationException,因为 AppTenant 在上述用户存储的构造函数中作为 null 传递。

我做错了什么?我播种的方式有误还是我忘记了一些基本的东西?

更新: 现在我采用了撬棍方法,避免了用户管理器并使用模拟 AppTenant 创建了我自己的用户存储实例:

if (!context.Users.Any(u => u.Email == admin.Email))
{
    var userStore = new TenantEnabledUserStore(context, new AppTenant
    {
        AppTenantId = 1
    });
    await userStore.SetPasswordHashAsync(admin, new PasswordHasher<ApplicationUser>().HashPassword(admin, "VeryStrongPassword123#"), default(CancellationToken));
    await userStore.SetSecurityStampAsync(admin, Guid.NewGuid().ToString("D"), default(CancellationToken));
    await userStore.CreateAsync(admin, default(CancellationToken));
}

尽管如此,我仍然对一种更干净的方法感兴趣,它不会让人觉得那么笨拙。

使用 Saaskit 时,您配置一个 AppTenantResolver,它决定如何根据提供的 HttpContext 设置 TenantContext<T>。然后它将检索到的 TenantContext<T> 存储在 HttpContextItems 属性 中。这是范围级缓存,因此租户仅在请求期间存储在那里。

当您将 AppTenant 注入 class 时,它会尝试从 HttpContext.Items 解析它。如果没有找到租户,那么它会注入空值。

当您调用 SeedData.Initialize(app.ApplicationServices) 时,您不在请求的上下文中,因此 AppTenantResolver 中间件没有 运行,也不会解析 AppTenant。

遗憾的是,由于没有您代码的完整详细信息,因此很难准确说明如何解决您的问题。您需要确保在 SeedData 方法中创建一个新的 Scope 并在该范围内解析一个 AppTenant ,以便后续对 IoC 的调用将允许插入它。