简体   繁体   中英

ASP.NET MVC Multi Tenant application with multiple databases

Introduction

I am working on a Multi Tenant application that I am programming in ASP.NET MVC with EF6 and Sql Server.

My database structure:

  • 1 base database that holds the tenant properties ( eg Name / Subdomain / Catalogue ).

    (Catalogue is the name of his assigned database )

  • 1 database for each customer

For knowing the tenant I use a subdomain lookup:

Entity Framework

Of the base and the default app database I have added a ADO.NET Entity data model ( edmx ) file.

Controllers

To get the correct tenant I have created a new custom controller that overrides the OnActionExecuting method. If the tenant exists I add the Id of the tenant to the route variables.

// Override OnActionExecuting method which is called every time before the action is called
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
    var fullAddress = filterContext.HttpContext.Request.Headers["Host"].Split('.');
    if (fullAddress.Length < 3)
    {
        // some code....
    }
    else
    {
        var tenantSubdomain = fullAddress[0];

        Account currentTenant = db.Accounts.FirstOrDefault(t => t.Subdomain.Equals(tenantSubdomain, StringComparison.CurrentCultureIgnoreCase));

        if (currentTenant != null)
            filterContext.RouteData.Values["tenant"] = currentTenant.Id;
        else
            filterContext.Result = new HttpStatusCodeResult(404);
    }

    base.OnActionExecuting(filterContext);
}

Till here everything is working fine. Now I get stuck for creating and storing the connection with the tenants assigned database.

To connect to the database I need to use the following code:

public SkycountAppEntities dbApp = new SkycountAppEntities(GetConnString(tenant.Catalogue)); 
//GetConnString renders the connectionstring which includes the tenants catalogue.

But where do I place this line so I do not have to call this in every action? Or can I cache it somewhere along with the authentication cookie?

Can someone guide me in the correct direction?

UPDATE

In this way its working but now I have to create the connection in every action of every controller.

// POST: Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Login([Bind(Include = "Username,Password")] User user, string returnUrl)
{
    using (SkycountAppEntities dbApp = new SkycountAppEntities(DbEntityFramework.RenderConnectionString(_SkycountAccount.Catalog)))
     {
         User _user = dbApp.Users.FirstOrDefault(u => u.Username.Equals(user.Username));
         if(_user != null && _user.Active && Crypto.VerifyHashedPassword(_user.Password, user.Password))
         {
             FormsAuthentication.SetAuthCookie(user.Username, false);

             if (String.IsNullOrEmpty(returnUrl) || !Url.IsLocalUrl(returnUrl))
                return RedirectToAction("Index", "Home");
             else
                return Redirect(returnUrl);
         } else
         {
             TempData["failed"] = true;

             return RedirectToAction("Login", new { returnUrl = returnUrl });
         }
    }
}

I alwayse use IoC to multi tenant application. In IoC I register Adapter with PerWebRequest LifeStyle to get configuration to current tenant from HttpContext. My addapter get in constructor Func to get Url from HttpContext.

public class CurrentTenantAdapter : ICurrentTenantAdapter
    {
        private readonly IGlobalCache cache;
        private readonly IMongoRepository repository;

        public CurrentTenantAdapter(Func<string> getTenantIdFunc, IMongoRepository repository, IGlobalCache cache)
        {
            this.GetTenantIdFunc = getTenantIdFunc;
            this.repository = repository;
            this.cache = cache;
        }

        public async Task<ITenant> GetTenantAsync()
        {
            string tenantId = GetTenantIdFunc();
            if (string.IsNullOrEmpty(tenantId))
            {
                return null;
            }

            Tenant tenant = await this.cache.GetAsync<Tenant>(tenantId);
            if (tenant == null)
            {
                tenant = await this.repository.QueryAsync(new GetTenantById(tenantId));
                if (tenant != null)
                {
                    await this.cache.StoreAsync(tenantId, tenant, null);
                }
            }

            return tenant;
        }

        public string GetTenantId()
        {
            return GetTenantIdFunc();
        }

        protected Func<string> GetTenantIdFunc { get; set; }
    }
}

This Adapter you can use in your repository. For example repository can create new EF context with connection string from tenant configuration. I use MongoDb with this adapter to get connection to right database.

private async Task<IMongoCollection<T>> ConnectDbAndGetCollectionAsync<T>(string databaseName)
        {
            var isMainModel = IsMainModel<T>();
            if (string.IsNullOrEmpty(databaseName))
            {
                databaseName = mainDatabaseName.Value;
                if (isMainModel == false && this.tenantConfiguration != null)
                {
                    ITenant tenant = await this.tenantConfiguration.GetTenantAsync();
                    DatabasesConfiguration databaseConfiguration = tenant.DatabasesConfiguration;
                    if (databaseConfiguration != null)
                    {
                        if (databaseConfiguration.MongoDatabaseConfiguration != null)
                        {
                            databaseName = databaseConfiguration.MongoDatabaseConfiguration.DatabaseName;
                        }
                    }
                }
            }

            var attribute = AttributesHelper.GetAttributeValue<MongoCollectionName>(typeof(T));
            string collectionName = attribute.CollectionName;

            IMongoDatabase db = await GetDatabaseAsync(databaseName);
            return await Task.FromResult(db.GetCollection<T>(collectionName));
        }

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