【问题标题】:Why is AppTenant null only while seeding data?为什么 AppTenant 仅在播种数据时为空?
【发布时间】:2016-08-26 19:23:04
【问题描述】:

我目前正在和 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));
}

尽管如此,我仍然对更干净的方法感兴趣,这种方法不会让人觉得很老套。

【问题讨论】:

  • 租户是要播种的数据的一部分吗?即第一个租户是否存在必须进行播种?
  • 这行不通。在示例中,您使用 new 关键字手动实例化 ApplicationDbContext。但在下一行中,您解析 UserManager&lt;ApplicationUser&gt;,这取决于 ApplicationDbContext。用户manage将收到ApplicationDbContext的不同实例,其中user尚未创建(因为SaveChanges方法在CreateAsync之后调用
  • 您也需要从 IoC 容器中解析您的``ApplicationDbContext`,但要小心。当您在 app.ApplicationServices 启动期间执行此操作时,您会创建一个单例(在应用程序启动期间没有请求)。您首先需要创建一个范围,然后从范围中解析并在最后处置范围。
  • 这是一个问题,因为租户是根据请求解析的,但无论如何用户存储都会调用 find 。也许在没有商店的情况下创建用户可能是一种有效的解决方法。
  • AppTenant 首先是如何被注入到上下文中的?是否通过工厂方法注册到 IoC 容器?

标签: c# asp.net asp.net-core asp.net-identity saaskit


【解决方案1】:

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

当您将AppTenant 注入到一个类中时,它会尝试从HttpContext.Items 解析它。如果没有找到租户,则它会注入 null。

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

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

【讨论】:

  • 你好安德鲁。我为此创建了一个 git 存储库。这应该可以更容易地加固代码,而不仅仅是 stackoverflow 上的片段。 github.com/Servellia/Saas-Demo/tree/master/src/…
  • 我查看了您的代码,我认为从根本上说,如果不在请求的上下文中,您将无法实现您想要实现的目标。您将无法通过 SeedData 类中的 DI 解析 AppTenant,因为它没有任何意义 - 如果您想为 2 个不同的 AppTenant 插入用户怎么办?我认为您提供的编辑可能是最好的解决方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-11
  • 2021-09-15
  • 1970-01-01
相关资源
最近更新 更多