I'm working on a multi-tenant web application using ASP.Net C# MVC 5. For reasons not relevant to this question, I want a separate database for each tenant, including the Identity 2.0 part. Also, I do not want to use the tenant name as hostname before my domain (ie http://tenant.myapp.com
). One obvious solution that's generally easy to implement is to have the tenant name in the MVC route configuration:
namespace MyApp
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{tenant}/{controller}/{action}/{id}",
defaults: new { tenant = "Demo", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
This works fine, but the approach seems to have one drawback. I was hoping to construct a connectionString at runtime, using the tenant from the RouteData.Values and passing that string to my ApplicationDbContext.
I suspect that, during initialization, the Identity framework is initialized as a singleton. From the template MVC code you get the following Startup partial.
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
...
}
}
The remark states that a single instance is created "per request", but this code looks to be hit only once when the app initializes, not per request. What I was hoping to do, is do something like this, passing the tenant into the constructor of ApplicationDbContext.
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
// Get the tenant from the routeData
string tenant = "YourCompany"; // Switch to something different from the default set during route configuration, for testing purposes.
if (HttpContext.Current.Request.RequestContext.RouteData.Values["tenant"] != null) tenant = HttpContext.Current.Request.RequestContext.RouteData.Values["tenant"].ToString();
// Configure the db context, user manager and signin manager to use a single instance per request
app.CreatePerOwinContext(() => ApplicationDbContext.Create(tenant));
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
...
}
}
But, alas, the HttpContext.Current.Request.RequestContext has no RouteData.Values since we're initiliazing when the code hits and there is no request. This code always results in a connectionString with 'YourCompany' as tenant.
I have developed several years ago exactly the same scenario...
Startup & RouteConfig are executed normally in global.asax/Application_Start , only once per iis-site start.
Try to create and set your DbContext in global.asax/Application_BeginRequest.
And some other hint: perhaps you should have only one DbContext per HttpRequest, by holding it in HttpContext.Current.Items["dbcontext"] for use in child actions or similiar.
Let me know if you succeed or need more hints
Frank
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.