简体   繁体   中英

Construct a connectionString at runtime from the url for ASP.Net MVC Identity 2

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM