具有多个数据库的ASP.NET MVC多租户应用程序 [英] ASP.NET MVC Multi Tenant application with multiple databases

查看:130
本文介绍了具有多个数据库的ASP.NET MVC多租户应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

简介

我正在使用在带有EF6和Sql Server的ASP.NET MVC中编程的多租户应用程序.

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

我的数据库结构:

  • 1个基本数据库,其中包含租户属性(例如Name/Subdomain/Catalog).

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

(目录是他分配的数据库的名称)

(Catalogue is the name of his assigned database )

每个客户1个数据库

为了了解租户,我使用子域查找:

For knowing the tenant I use a subdomain lookup:

  • eg http://customer1.app.site.com
  • eg http://customer2.app.site.com
  • eg http://customer3.app.site.com

实体框架

在基本数据库和默认应用数据库中,我添加了ADO.NET Entity data model(edmx)文件.

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

控制器

为获得正确的租户,我创建了一个新的自定义控制器,该控制器将覆盖OnActionExecuting方法.如果租户存在,则将租户的ID添加到路由变量中.

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.

但是我应该在哪里放置此行,这样就不必在每次操作中都调用此行?还是可以将其与身份验证Cookie一起缓存在某处?

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?

更新

以这种方式工作,但是现在我必须在每个控制器的每个动作中创建连接.

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 });
         }
    }
}

推荐答案

我总是使用IoC来实现多租户应用程序.在IoC中,我向PerWebRequest LifeStyle注册了Adapter,以从HttpContext获取当前租户的配置.我的适配器进入构造函数Func,以从HttpContext获取网址.

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; }
    }
}

您可以在存储库中使用的此适配器.例如,存储库可以使用租户配置中的连接字符串创建新的EF上下文.我将MongoDb与该适配器一起使用以连接到正确的数据库.

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));
        }

这篇关于具有多个数据库的ASP.NET MVC多租户应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆