3

I am creating a web application works off of a multitenancy structure. As such, each user request will lock that user a specific tenant based on the hostname (with different databases per tenant.) I can set a general context's connection string, but I am at a loss right now on figuring out how to change the connection string for the ASP.NET Core 2.0 Identity objects.

I see that the default way that they have us doing it is setting the UseAuthentication() in StartUp.cs. And then when the controller is called, DI then sets the UserManager and SignInManager objects. I am assuming I need to create new UserManager and SignInManager objects within each controller constructor passing the specific connection string for that tenant (not using DI at all.)

Any thoughts on this?

Clarification:

My hope is to figure out if it is possible to change the database connection string for a passed identity object to a controller by dependency injection.

1 Answer 1

1
// Startup class of asp.net core 2
public void ConfigureServices(IServiceCollection services)
{
    services.AddMultitenancy<Tenant, TenantResolver>();

    string accountsConnection = configuration.GetConnectionString("AccountsConnectionString");

    services.AddScoped<LicenseManager>();
    services.AddScoped<AccountManager>();


    services.AddEntityFrameworkSqlServer()
        .AddDbContext<AccountsContext>(options =>
        {
            options.UseSqlServer(accountsConnection);
        })
        .AddDbContext<MasterDataContext>(options => options.UseSqlServer(masterdataConnection))
        .AddDbContext<DataContext>();

    services.AddMvc();
}

// Tenantresolver which get's the connectionstring from the claims. (seperate file/class)
// On authentication i create several claims with one claim with the connectionstring per user. 
// I use a seperate accounts database where all the tenants are saved which each of them has his own connectionstring
public class TenantResolver : MemoryCacheTenantResolver<Tenant>
{
    private readonly AccountsContext Context;

    public TenantResolver(AccountsContext context, IMemoryCache cache, ILoggerFactory logger)
        :base(cache, logger)
    {
        Context = context;
    }

    protected override string GetContextIdentifier(HttpContext context)
    {
        return context.User.Claims.Where(r => r.Type == ClaimTypes.AccountIdType).Select(s => s.Value).SingleOrDefault();
    }

    protected override IEnumerable<string> GetTenantIdentifiers(TenantContext<Tenant> context)
    {
        return new[] { context.Tenant.Tenant_Id.ToString() };
    }

    protected override Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
    {
        TenantContext<Tenant> tenantContext = null;

        string claim = context.User.Claims.Where(r => r.Type == ClaimTypes.AccountIdType).Select(s => s.Value).SingleOrDefault();

        var tenant = Context.Accounts
                .Where(q => q.IsActive == true && q.Account_Id.ToString() == claim)
                .Select(s => new Tenant(s.Account_Id, s.DataStore.DatabaseConnection))
                .FirstOrDefault();

        if (tenant != null)
        {
            tenantContext = new TenantContext<Tenant>(tenant);
        }

        return Task.FromResult(tenantContext);
    }
}

// Tenant class where i save the info needed.
public class Tenant
{
    public Tenant(
        Guid tenant_id,
        string database_ConnectionString)
    {
        Tenant_Id = tenant_id;
        Database_ConnectionString = database_ConnectionString;
    }

    public Guid Tenant_Id { get; set; }
    public string Database_ConnectionString { get; set; }
}

// DbContext class which uses the tenant and links the connectionstring to the current tenant.
public class DataContext : DbContext
{
    private readonly Tenant Tenant;

    public DataContext(DbContextOptions<DataContext> options, Tenant tenant)
        : base(options)
    {
        this.Tenant = tenant;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (Tenant != null)
        {
            optionsBuilder.UseSqlServer(Tenant.Database_ConnectionString);
        }

        base.OnConfiguring(optionsBuilder);
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

FYI, I used a seprate database (accounts) where I store the tenants and their users, licenses and settings. ;)
I see that in this code you will get a connection string that can be used for the specific tenant's database. How would you apply that connection string to the identity objects needed by the AccountController?
I created a custom user object which has an account_id (is the same as Tenant_Id). I use OpenIdDict for oauth authentication which has an exchange method where the authentication ticket is created. In this ticket (claims) I add an extra claim with this id. (You could use this place also to save your connectionstring, but then you have to use a kind of claimsaccessor to convert it to an tenant with connectionstring). The tenantresolver uses this id to resolve the connectionstring. If you want I have an example of this.
I think that your setup is maybe a little too custom or specific for your needs than what I am wanting (though I may end up rolling something more custom.) I am looking for just a way to change the connection string on an already existing UserStore identity object that is passed to a controller.
The only possible way to do that as far as I know is to add a custom claim to the auth ticket and use it in the datacontext as shown above.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.