views:

600

answers:

4

I would like to setup a multi-tenant ASP.NET MVC app. Ideally, this app would have a route with {tenant}/{controller}/{action}/{id}, each tenant representing an logical instance of the app (simply independent multi-user accounts)

The fine grained details how do that are still quite unclear to me. Any guide available to setup such multi-tenant scheme with ASP.NET MVC?

A: 

if you don't mind using the S#arp architecture (opinionated Asp.Net MVC) , here's a guide to make S#arp multi-tenant

http://www.iamnotmyself.com/2009/08/08/ImplementingAMultiTenantASPNETMVCApplicationWithSarpArchitecture.aspx

AndreasKnudsen
+2  A: 

You will prob find these links useful.

cottsak
The question linked is relevant indeed, but with no good answers posted unfortunately. Actually, the only relevant post links to yet another post, with even less relevant answers :-(
Joannes Vermorel
That sux. i guess i'll just wait till you find the answer then and i'll use what you find then. :D
cottsak
Thanks, these links are great!
Chris Melinn
A: 

I just thought this article is quite good:

Build multi tenant applications on aspnet MVC

tuanvt
+4  A: 

I am currently working on a similar project using ASP.Net MVC, Forms Authentication and the SQL providers for Membership/Roles/Profile. Here is the approach I am taking:

  1. Register the default route as `{tenant}/{controller}/{action}/{id}

  2. Change the default behavior of the FormsAuthenticationService that comes with the standard MVC template. It should set the UserData of the authentication ticket to include the tenant name (from your route).

    public void SignIn(string userName, bool createPersistentCookie, string tenantName)
    {
        var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.AddMinutes(30),
                                                   createPersistentCookie, tenantName);
        var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
        HttpContext.Current.Response.AppendCookie(cookie);
    }
    
  3. In your global.asax file to do some tenant security checking and allow partioning of users between tenants in one membership database

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        //Since this method is called on every request
        //we want to fail as early as possible
        if (!Request.IsAuthenticated) return;
        var route = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context));
        if (route == null || route.Route.GetType().Name == "IgnoreRouteInternal") return;
        if (!(Context.User.Identity is FormsIdentity)) return;
        //Get the current tenant specified in URL 
        var currentTenant = route.GetRequiredString("tenant");
        //Get the tenant that that the user is logged into
        //from the Forms Authentication Ticket
        var id = (FormsIdentity)Context.User.Identity;
        var userTenant = id.Ticket.UserData;
        if (userTenant.Trim().ToLower() != currentTenant.Trim().ToLower())
        {
            //The user is attempting to access a different tenant
            //than the one they logged into so sign them out
            //an and redirect to the home page of the new tenant
            //where they can sign back in (if they are authorized!)
            FormsAuthentication.SignOut();
            Response.Redirect("/" + currentTenant);
            return;
        }
        //Set the application of the Sql Providers 
        //to the current tenant to support partitioning
        //of users between tenants.
        Membership.ApplicationName = currentTenant;
        Roles.ApplicationName = currentTenant;
        ProfileManager.ApplicationName = currentTenant;
    }
    
  4. Partition each tenants data. Here are two options:

    4a. Use a separate database for each tenant. This provides the best data security for your tenants. In the shared membership database, add a table that is keyed on unique appid for each tenant and use this table to store and retrieve the connection string based on the current tenant.

    4b. Store all data in one database and key each table on the unique tenant id. This provides slightly less data security for your tenants but uses only one SQL Server license.

Jeff French
Sorry for the out of date reply, but I am working on something similar and I think your solution might work, however the docs say that there is only a single default provider for all requests coming in to the server. So, I think setting the application name may be a race condition.
CShipley
CShipley, you are absolutely right. When I used this solution myself I almost went crazy trying to troubleshoot problems once I had concurrent users from different tenants. I think the way to is to write the authentication piece from scratch or implement your own membership provider. I chose to move to a separate instance of the app for each tenant until I could get the details of a custom authentication scheme worked out.
Jeff French