【发布时间】: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<ApplicationUser>,这取决于ApplicationDbContext。用户manage将收到ApplicationDbContext的不同实例,其中user尚未创建(因为SaveChanges方法在CreateAsync之后调用 -
您也需要从 IoC 容器中解析您的``ApplicationDbContext`,但要小心。当您在 app.ApplicationServices 启动期间执行此操作时,您会创建一个单例(在应用程序启动期间没有请求)。您首先需要创建一个范围,然后从范围中解析并在最后处置范围。
-
这是一个问题,因为租户是根据请求解析的,但无论如何用户存储都会调用 find 。也许在没有商店的情况下创建用户可能是一种有效的解决方法。
-
AppTenant首先是如何被注入到上下文中的?是否通过工厂方法注册到 IoC 容器?
标签: c# asp.net asp.net-core asp.net-identity saaskit