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

时间:2016-08-26 19:23:05

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

我目前正与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已正确设置。在SeedData.Initialize(app.ApplicationServices);方法结束时调用Statup.Configure()时会出现问题:

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();
        }
    }
}

usermanager正在调用自定义用户界面,但现在AppTenant为null。 当代码最终到达

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));
}

尽管如此,我仍然对一种更干净的方法感兴趣,并不觉得这种方法很糟糕。

1 个答案:

答案 0 :(得分:2)

使用Saaskit时,您需要配置AppTenantResolver,以确定如何根据提供的TenantContext<T>设置HttpContext。然后,它会将检索到的TenantContext<T>存储在Items的{​​{1}}属性中。这是一个Scope级别缓存,因此租户仅在请求期间存储在那里。

当您将HttpContext注入某个类时,它会尝试从AppTenant解析它。如果找不到租户,则会注入null。

当您致电HttpContext.Items时,您不在请求的上下文中,因此SeedData.Initialize(app.ApplicationServices)中间件尚未运行,并且无法解析AppTenant。

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