【问题标题】:MVC 6 (vNext) UserManager - DI - create user in specific databaseMVC 6 (vNext) UserManager - DI - 在特定数据库中创建用户
【发布时间】:2016-01-03 15:55:26
【问题描述】:

我正在开发多租户应用程序。我为每个租户使用单独的数据库。 UserData 也适用于单独数据库中的每个租户。

我的问题是如何在 DI 上独立地为“自定义”数据库中的每个租户创建管理员帐户。在 MVC 5 中,可以基于 UserStore(连接字符串)实例化 UserManager。但是 mvc6 中的 UserManager 依赖于 HttpContext...没有找到文档... 存在请某种方式怎么做???我在 mvc 6 中需要类似 mvc 5 的东西:

 UserStore<TenantUser> store = new UserStore<TenantUser>(new TenantDbContext("CONNECTION STRING"));  //!!!  NO POSSIBLE CREATE USER IN CUSTOM DATABASE
        UserManager<TenantUser> t = new UserManager<TenantUser>(store);
        t.CreateAsync(user, password);

更新:

public class TenantDbContext : IdentityDbContext<TenantUser, TenantRole, Guid>
{
    private string _connectionString { get; set; }
    private readonly IHttpContextAccessor _contextAccessor;
    private readonly ApplicationDbContext _applicationDbContext;

    //THIS SUB UNCOMENT ONLY IF CREATE MIGRATIONS (dnx ef...)
    /*  
    public TenantDbContext(DbContextOptions<TenantDbContext> options) : base(options) 
    {
        this._connectionString = "CONNECTION STRING";
    }
    */
    public TenantDbContext(DbContextOptions<TenantDbContext> options, IHttpContextAccessor contextAccessor, ApplicationDbContext applicationDbContext) : base(options)          {                            
        _contextAccessor = contextAccessor;
        _applicationDbContext = applicationDbContext;
        TenantResolver resolver = new TenantResolver(_contextAccessor, _applicationDbContext);
        string con = resolver.GetConnectionString();
        if (con != string.Empty)
        {
            this._connectionString = con;                            }
        else
        {
            this._connectionString = "CONNECTION STRING"; //Development connection string
        }
    }

    public TenantDbContext()        //Posibility to create TenantDbContext migration and development database with no connectionString in constructor
    {
        //this._connectionString = "CONNECTION STRING";
    }   
    public TenantDbContext(string ConnectionString)
    {
        this._connectionString = ConnectionString;
    }
    public static TenantDbContext Create(string ConnectionString)
    {
        return new TenantDbContext(ConnectionString);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    { 
        optionsBuilder.UseSqlServer(_connectionString);  
    }

【问题讨论】:

  • 有很多方法可以解决您的问题。 需要了解更多信息。 您是否在appsettings.json 中定义了您需要的两个连接字符串?您是如何创建TenantDbContext(手动,使用dnx ef dbcontext scaffold)的?你有两个上下文(TenantDbContext1TenantDbContext2)。在 ASP.NET 5 中使用的一种通常是依赖注入。它允许在Startup.csConfigureServices 中注册所有必需的上下文,new DbContext 将被服务调用并转发给 Controller 的构造函数或控制器的每个 Action。
  • 我创建了两个 DbContext:ApplicationDbContext 和 TenantDbContext。 ApplicationDbContext 用于所有共享数据。TenantDbContext 取决于子域(ConectionString 基于子域解析)。对于 DbContexts 使用 DI...问题是如何在新数据库中创建用户...我无法在启动服务中注册 TenantDbContext。它取决于连接字符串(许多租户......)
  • 您能否发布更多代码片段,它准确地说明了您所做的事情。如果您写“许多租户”,您的意思是什么。你有一些特定的上下文列表吗?您在哪种情况下知道详细信息(连接字符串)?例如,我可以向您展示如何动态更改 one 上下文的ConnectionString,但可能需要关闭现有连接并重新打开另一个连接。
  • 请查看更新.. 许多租户 = 一个具有多个连接字符串的 TenantDbContext 类。连接字符串是在运行时从数据库中加载的......请参阅 TenantResolver 解析器......
  • 我要离开几个小时,所以我刚刚发布了我的答案。

标签: asp.net-mvc asp.net-identity asp.net-core-mvc asp.net-core


【解决方案1】:

希望我能正确理解你。让我们忘记已经对数据库开放的多个连接的性能和缓存。我想您有 多个 数据库,它们具有相同的架构。您需要使用(共享)相同的数据库上下文来访问数据库。

我可以建议您两个解决方案。

第一个解决方案包括注册一个上下文并重新打开如果打开的上下文改变了连接字符串。

让我们给你TenantDbContext,它可以用不同的目标数据库打开。例如与

@"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;"

@"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;"

首先你删除OnConfiguringlike

protected override void OnConfiguring(DbContextOptionsBuilder options)
{
    options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TenantDb;Trusted_Connection=True;");
}

这可能存在于TenantDbContext 的定义中,并且您在Startup.csConfigureServices 中使用以下代码:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    var connection1 = @"Server=(localdb)\mssqllocaldb;Database=TenantDb1;Trusted_Connection=True;";

    services.AddEntityFramework()
        .AddSqlServer()
        .AddDbContext<TenantDbContext>(options => options.UseSqlServer(connection1));

    services.AddMvc();
    ...
}

以您从数据库 (TenantDb1) 中注入 TenantDbContext 的方式。让我们 TenantDbContext 包含一些实体集,例如 Blog。因此,您可以通过以下方式定义一些 MVC 控制器

public class TenantsController : Controller
{
    private TenantDbContext _context;

    public TenantsController (TenantDbContext context)
    {
        _context = context;
    }

    public IActionResult Index() {
        var con = _context.Database.GetDbConnection();

        // now the con uses either TenantDb2 or TenantDb2
        // con.ConnectionString can be used to get or set the 
        // connection string 
        string needConStr = @"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;";
        if (con.ConnectionString != needConStr) { // can be compared more carefully
            _context.Database.CloseConnection();
            con.ConnectionString = needConStr;
        }

        // get some data from the TenantDbContext
        var blog = _context.Blog.ToList();
        return View(blog);
    }
}

第二种解决方案不需要使用DependencyInjection注入任何TenantDbContext。取而代之的是,您只需要添加一个简单的构造函数到TenantDbContext

public TenantDbContext(DbContextOptions optionsBuilder): base (optionsBuilder)
{
}

这样简单的构造函数将允许您在需要时随时创建上下文:

public class TenantsController : Controller
{
    public IActionResult Index() {
        var contextOptions = new DbContextOptionsBuilder();
        contextOptions.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TenantDb2;Trusted_Connection=True;");
        var context = new BloggingContext(contextOptions.Options);
        context.Database.OpenConnection();

        // get some data from the TenantDbContext
        var blog = context.Blog.ToList();
        return View(blog);
    }
}

我直接在代码中使用了所有的连接字符串。您可以轻松修改上述代码以从配置文件appsettings.json 中获取所有连接字符串。

【讨论】:

  • 感谢您的回复。我不使用这个:public TenantDbContext(DbContextOptions options) : base(options)。我使用这个: public TenantDbContext(DbContextOptions options, IHttpContextAccessor contextAccessor, ApplicationDbContext applicationDbContext) : base(options) .... 在控制器中注入 TenantDbContext 和基于 url 的特定连接字符串。 tenant1.app.com 和tenant2.app.com 具有相同的TenantDbContexts 不同的连接字符串...问题只是如何添加到每个租户数据库管理员用户,
  • 直到 UserManager 依赖于“url”.. 解决方案是手动创建 UserManager 的实例并将我的自定义 UserStore 注入到构造函数中。 UserManager 的其余部分是从 DI 注入的。
  • 我会根据你从配置文件中获取连接字符串。在配置中可以是开发数据库或演示的连接字符串,直到每个租户的特定连接字符串可以动态解析(DI)...
  • @MiroslavSiska:我自己不使用个人用户帐户,因此我将您的问题理解为常见的数据库问题。我不确定它是否对您有帮助,但总的来说,您应该只拥有数据库,您可以根据 DbContext 访问该数据库。因此,您是否在内部拥有用户和角色并不重要。我希望它能以某种方式帮助你。如果它没有帮助,那么您可以尝试向 asp.net 5 身份 3 专家解决问题。
【解决方案2】:

解决了。 1. 创建CustomUserStore

public class TenantUserStore : UserStore<TenantUser, TenantRole, TenantDbContext, Guid>
{
    public TenantUserStore(TenantDbContext context, IdentityErrorDescriber describer = null): base(context, describer)
    {  
    }
}

这里是如何使用自定义数据库实例化 UserManager 的代码:

IUserStore<TenantUser> CustomStore = new TenantUserStore(new TenantDbContext(coonection), null);    

UserManager<TenantUser> manager = new UserManager<TenantUser>(CustomStore, _optionsAccessor, _passwordHasher, _userValidators,
                    _passwordValidators, _keyNormalizer, _errors, _services, _logger, _contextAccessor);

而 DI 仅用于 UserManager 构造函数的其余部分:

       public class TenantsController : Controller
{
    private readonly IHttpContextAccessor _contextAccessor;        
    private readonly IOptions<IdentityOptions> _optionsAccessor;
    private readonly IPasswordHasher<TenantUser> _passwordHasher;
    private readonly IEnumerable<IUserValidator<TenantUser>> _userValidators;
    private readonly IEnumerable<IPasswordValidator<TenantUser>> _passwordValidators;
    private readonly ILookupNormalizer _keyNormalizer;
    private readonly IdentityErrorDescriber _errors;
    private readonly IServiceProvider _services;
    private readonly ILogger<UserManager<TenantUser>> _logger;


    public TenantsController(IHttpContextAccessor contextAccessor, 
         IOptions<IdentityOptions> optionsAccessor,
         IPasswordHasher<TenantUser> passwordHasher,
         IEnumerable<IUserValidator<TenantUser>> userValidators,
         IEnumerable<IPasswordValidator<TenantUser>> passwordValidators,
         ILookupNormalizer keyNormalizer,
         IdentityErrorDescriber errors,
         IServiceProvider services,
         ILogger<UserManager<TenantUser>> logger


        )
    {
         _optionsAccessor = optionsAccessor;
        _passwordHasher = passwordHasher;
        _userValidators = userValidators;
        _passwordValidators = passwordValidators;
        _keyNormalizer = keyNormalizer;
        _errors = errors;
        _services = services;
        _logger = logger;            
        _contextAccessor = contextAccessor;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-13
    相关资源
    最近更新 更多