Reputation: 7794
I am trying to build a multi-tenant mvc 5 site that uses a single database and differentiates the tenants by schema in Sql Server. I started with the default Mvc 5 template and updated the ApplicationDBContext that is provided to take a string specifying the schema to use for that tenant like so.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private string _tenantSchema;
public ApplicationDbContext(string tenantSchema)
: base("Dev", throwIfV1Schema: false)
{
_tenantSchema = tenantSchema;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(_tenantSchema);
base.OnModelCreating(modelBuilder);
}
public static ApplicationDbContext Create(string tenantSchema)
{
return new ApplicationDbContext(tenantSchema);
}
}
And then in the App_Start\IdentityConfig.cs I updated the Create Method of the ApplicationUserClass to use the first part of the Request.Host value to use as the tenantSchema like so
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var tenantSchema = context.Request.Host.Value.Split('.')[0];
var ctx = new ApplicationDbContext(tenantSchema);
var userStore = new UserStore<ApplicationUser>(ctx);
var manager = new ApplicationUserManager(userStore);
so if I were to log into site1.mysite.dev it would authenticate against the tables in the site1 schema is sql server.
When I start the site and access it using the site1 sub domain it correctly uses the site1 Schema to authenticate me. But then if I change url in the browser address bar and log in again it still validates against the site1 schema.
How can I configure the app to use to correct schema to check authentication for each request?
Upvotes: 1
Views: 1143
Reputation: 93424
While the code you've shown should sort of work (but it's hard to tell since you left out other key parts, such as Startup.Auth), I would not do it in this way. I would instead change your ApplicationDbContext.Create
method to this:
public static ApplicationDbContext Create(
IdentityFactoryOptions<ApplicationDbContext> options, IOwinContext context)
{
var tenantSchema = context.Request.Host.Value.Split('.')[0];
return new ApplicationDbContext(tenantSchema);
}
I would then alter my Startup.Auth as such:
app.CreatePerOwinContext<ApplicationDbContext>(ApplicationDbContext.Create); <---
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
And leave everything else the default. Specifically:
public static ApplicationUserManager Create(
IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(
new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
....
However, your real problem is most likely that your model builder is cached in the app domain the first time it is created. This is referenced in the documentation here:
http://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext.onmodelcreating(v=vs.113).aspx
Typically, this method is called only once when the first instance of a derived context is created. The model for that context is then cached and is for all further instances of the context in the app domain. This caching can be disabled by setting the ModelCaching property on the given ModelBuidler, but note that this can seriously degrade performance. More control over caching is provided through use of the DbModelBuilder and DbContextFactory classes directly.
There is actually a nice discussion on this very topic relating to EF and multi-tenant usage (way too long to elaborate here) over at the EF codeplex site:
https://entityframework.codeplex.com/discussions/462765
Upvotes: 1