为什么 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>
存储在 HttpContext
的 Items
属性 中。这是范围级缓存,因此租户仅在请求期间存储在那里。
当您将 AppTenant
注入 class 时,它会尝试从 HttpContext.Items
解析它。如果没有找到租户,那么它会注入空值。
当您调用 SeedData.Initialize(app.ApplicationServices)
时,您不在请求的上下文中,因此 AppTenantResolver
中间件没有 运行,也不会解析 AppTenant。
遗憾的是,由于没有您代码的完整详细信息,因此很难准确说明如何解决您的问题。您需要确保在 SeedData
方法中创建一个新的 Scope
并在该范围内解析一个 AppTenant
,以便后续对 IoC 的调用将允许插入它。
我目前正在和 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>
存储在 HttpContext
的 Items
属性 中。这是范围级缓存,因此租户仅在请求期间存储在那里。
当您将 AppTenant
注入 class 时,它会尝试从 HttpContext.Items
解析它。如果没有找到租户,那么它会注入空值。
当您调用 SeedData.Initialize(app.ApplicationServices)
时,您不在请求的上下文中,因此 AppTenantResolver
中间件没有 运行,也不会解析 AppTenant。
遗憾的是,由于没有您代码的完整详细信息,因此很难准确说明如何解决您的问题。您需要确保在 SeedData
方法中创建一个新的 Scope
并在该范围内解析一个 AppTenant
,以便后续对 IoC 的调用将允许插入它。