C# 为什么AppTenant仅在设定数据种子时为null?

C# 为什么AppTenant仅在设定数据种子时为null?,c#,asp.net,asp.net-core,asp.net-identity,saaskit,C#,Asp.net,Asp.net Core,Asp.net Identity,Saaskit,我现在和本·福斯特在一起 我使用AppTenantId属性扩展了ApplicationUser,并创建了一个自定义UserStore,它使用AppTenant标识用户: public class TenantEnabledUserStore : IUserStore<ApplicationUser>, IUserLoginStore<ApplicationUser>, IUserPasswordStore<ApplicationUser>, IUser

我现在和本·福斯特在一起

我使用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*/
}
usermanager正在调用自定义userstore,但现在AppTenant为null。 当代码最终到达

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

尽管如此,我仍然对一种更干净的方法感兴趣,这种方法不会让人觉得有什么不妥。

在使用Saaskit时,您可以配置一个
AppTenantResolver
,它根据提供的
HttpContext
确定如何设置
TenantContext
。然后,它将检索到的
TenantContext
存储在
HttpContext
Items
属性中。这是一个作用域级缓存,因此租户仅在请求期间存储在那里

AppTenant
注入类时,它会尝试从
HttpContext.Items
解析它。如果找不到租户,那么它将改为注入null

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


不幸的是,由于没有完整的代码细节,很难说清楚如何解决您的问题。您需要确保在
SeedData
方法中创建一个新的
Scope
,并在该Scope内解析一个
AppTenant
,以便随后对IoC的调用将允许插入它

使用Saaskit时,您可以配置一个
AppTenantResolver
,它根据提供的
HttpContext
确定如何设置
TenantContext
。然后,它将检索到的
TenantContext
存储在
HttpContext
Items
属性中。这是一个作用域级缓存,因此租户仅在请求期间存储在那里

AppTenant
注入类时,它会尝试从
HttpContext.Items
解析它。如果找不到租户,那么它将改为注入null

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


不幸的是,由于没有完整的代码细节,很难说清楚如何解决您的问题。您需要确保在
SeedData
方法中创建一个新的
Scope
,并在该Scope内解析一个
AppTenant
,以便随后对IoC的调用将允许插入它

租户是要播种的数据的一部分吗?也就是说,第一个租户必须存在吗?那是行不通的。在本例中,使用
new
关键字手动实例化
ApplicationDbContext
。但在下一行中,您将解析取决于
ApplicationDbContext
UserManager
。用户管理器将收到另一个
ApplicationDbContext
实例,其中尚未创建
user
(因为
SaveChanges
方法是在
CreateAsync
之后调用的,所以您也需要从IoC容器中解析您的``ApplicationDbContext`,但要小心。当您在启动app.ApplicationServices时这样做,您会创建一个单例(在应用程序启动期间没有请求)。您首先需要创建一个作用域,然后从该作用域解析并在最后处置该作用域。这会带来一个问题,因为租户是根据请求解析的,但userstore在任何情况下都会调用find。可能创建没有该存储的用户是一个有效的解决方法。如何将
AppTenant
注入到上下文i中n首先?您是否通过工厂方法将其注册到IoC容器中?租户是否是要播种的数据的一部分?即第一个租户存在时是否必须进行播种?这将不起作用。在示例中,您使用
new
关键字手动实例化
ApplicationDbContext
。但在下一行中,您将olve
UserManager
,它取决于
ApplicationDbContext
。用户管理器将收到另一个
ApplicationDbContext
实例,其中尚未创建
用户
(因为
SaveChanges
方法是在
CreateAsync
之后调用的,所以您也需要从IoC容器中解析您的``ApplicationDbContext`,但要小心。当您在启动app.ApplicationServices时这样做,您会创建一个单例(在应用程序启动期间没有请求)。您首先需要创建一个作用域,然后从该作用域解析并在最后处置该作用域。这会带来一个问题,因为租户是根据请求解析的,但userstore在任何情况下都会调用find。可能创建没有该存储的用户是一个有效的解决方法。如何将
AppTenant
注入到上下文i中首先?您是否通过工厂方法将其注册到IoC容器中?您好,Andrew。我已经为此创建了一个git存储库。这将使代码更容易巩固,而不仅仅是stackoverflow上的片段。我已经查看了您的代码,我认为,从根本上讲,如果不这样做,您试图实现的目标是不可能实现的在请求的上下文中。您将无法通过SeedData类中的DI解析AppTenant,因为这没有实际意义-如果您想为两个不同的AppTenant插入用户该怎么办?我认为您提供的编辑可能是最好的解决方法。您好,Andrew。我已经为此创建了一个git存储库。这应该会更容易o巩固代码,而不仅仅是stackoverflow上的片段。我已经查看了您的代码,我认为您试图实现的基本目标不会是
public Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
    return _context.Users.FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.AppTenantId == _tenant.AppTenantId, cancellationToken);
}
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));
}