代码之家  ›  专栏  ›  技术社区  ›  Fabricio Rodriguez

多租户.NET核心Web API

  •  2
  • Fabricio Rodriguez  · 技术社区  · 7 年前

    我需要为现有系统构建一个web api。全国各地都有不同的机构,每个机构都有自己的数据库。所有数据库都在一台服务器上。所有数据库的结构都是相同的。所有数据库都有自己的用户名和密码。一个机构有一个或多个用户。用户可以属于一个或多个机构。还有一个特殊的数据库,其中包含一个包含所有用户的表、一个包含所有代理的表和用户代理桥表。

    目前他们正在使用传统的windows桌面应用程序。当用户设置此windows程序时,他们将使用用户名和密码登录。然后,系统会为他们显示他们所属的所有机构的列表(通常只有一个,但一些“超级用户”可能属于少数)。他们选择一个代理,然后程序连接到正确的数据库。对于会话的其余部分,用户所做的一切都将在该数据库上完成。

    客户端希望创建一个web应用程序来最终替换windows程序(这两个程序将同时运行一段时间)。一个开发人员正在Angular5中创建前端,我正在ASP.NET Core2.1中开发API。

    因此,web应用程序将以类似于windows应用程序的方式运行。用户登录到Web应用程序。使用我的web api的web应用程序告诉api哪个用户刚刚登录。然后,api从存储该数据的数据库中检查该用户属于哪个机构。api返回用户属于web应用程序的代理列表。在那里,用户选择一个代理。从现在起,web应用程序将在所有api调用的头中包含此代理id。当api从web应用程序接收到请求时,它将根据请求头中的代理id知道要使用哪个数据库。

    希望这有道理…

    显然,这意味着我必须动态地更改dbcontext的连接字符串,这取决于api必须与哪个数据库通信。我一直在研究这个问题,首先是在控制器上做,这很有效,但是在我的所有控制器中都会涉及到大量的复制和粘贴反模式。所以我试图将其移到dbcontext的onconfiguring事件中。我想最好创建一个dbcontext工厂来创建dbcontext,使用适当的连接字符串。我只是有点迷路了。你看,当web应用程序调用web api上的一个端点(比方说,一个http get请求来获取一个帐户列表)时,这将在accounts控制器中触发httpget处理程序。然后,此操作方法读取代理ID头。但这一切都发生在控制器上…如果我从dbcontext的onconfiguring()事件调用dbcontext工厂,它必须将代理id(在控制器中读取)发送到工厂,以便工厂知道要创建哪个连接字符串。我试图不使用全局变量来保持类的松散耦合。

    除非我有一些服务在管道中运行,截取所有请求,读取代理id头,然后以某种方式将其注入到dbcontext构造函数中?不知道我会怎么做…

    总之,我有点迷路了。我甚至不确定这是不是正确的方法。我看过一些“多租户”的例子,但老实说,我发现它们有点难以理解,我希望我现在可以做一些更简单的事情,随着时间的推移,随着我对.NET核心知识的提高,我可以考虑相应地改进代码。

    1 回复  |  直到 7 年前
        1
  •  2
  •   Riscie    7 年前

    我正在研究类似的东西,你在这里描述。由于我还处于起步阶段,我还没有银弹。但是有一件事可以帮助你的方法:

    首先在控制器上进行,这很有效,但是在我的所有控制器中都会涉及到大量的复制和粘贴反模式。

    我采用了让中间件负责交换dbconnection字符串的方法。像这样的:

    public class TenantIdentifier
    {
        private readonly RequestDelegate _next;
    
        public TenantIdentifier(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task Invoke(HttpContext httpContext, GlobalDbContext dbContext)
        {
            var tenantGuid = httpContext.Request.Headers["X-Tenant-Guid"].FirstOrDefault();
            if (!string.IsNullOrEmpty(tenantGuid))
            {
                var tenant = dbContext.Tenants.FirstOrDefault(t => t.Guid.ToString() == tenantGuid);
                httpContext.Items["TENANT"] = tenant;
            }
    
            await _next.Invoke(httpContext);
        }
    }
    
    
    public static class TenantIdentifierExtension
    {
        public static IApplicationBuilder UseTenantIdentifier(this IApplicationBuilder app)
        {
            app.UseMiddleware<TenantIdentifier>();
            return app;
        }
    }
    

    在这里,我使用一个自己创建的http头,名为 X-Tenant-Guid 以标识租户guid。然后我向全局数据库发出一个请求,在那里我得到这个租户数据库的连接字符串。

    我在这里公开了这个例子。 https://github.com/riscie/ASP.NET-Core-Multi-Tenant-multi-db-Example (它还没有更新到ASP Net Core 2.1,但快速更新应该不是问题)