【问题标题】:How to implement Web API with many databases depending on client calling如何根据客户端调用实现具有多个数据库的 Web API
【发布时间】:2018-01-06 20:01:17
【问题描述】:

我必须为一个系统创建一个 RESTful Web API(我使用的是 ASP.NET Core 1.1),这可能有点不寻常,因为每个使用该系统的客户端都有自己的数据库。 (客户就像一家公司,里面有很多用户)。所以有一个主数据库,它为每个客户端指定它必须连接到哪个数据库。所有客户端数据库都是相同的(当然数据除外)。

我想知道我应该如何处理这个...通常,当应用使用 Web API 时,您可能会遇到这样的情况:

public class AccountsController : Controller
{
    private readonly SomeContext _context;

    public FilesController(SomeContext context)
    {
        _context = context;
    }

    // GET: api/Files
    [HttpGet)]
    public IEnumerable<Account> GetAccounts()
    {
            return _context.Accounts;
    }
}

现在,在上面的示例中,问题在于上下文必须指向不同的数据库,具体取决于哪个客户端正在请求帐户列表。我不确定如何最好地解决这个问题。如果前端在每个 API 调用中都在查询字符串中发送一个客户端标识符,是否可以?因此,每个控制器操作都会有一个 ClientId 参数。并且基于此参数,该操作将在调用之前即时修改上下文的连接字符串?不过,这听起来很糟糕——无论是从实现的角度还是从安全的角度来看。问题是,据我所知,ASP.NET 是无状态的,所以每次调用 API 时,API 都不知道这是谁?

或者我还能怎么做?

【问题讨论】:

  • 对不起,我刚刚意识到我可以使用来自身份验证服务器的 access_token 值(我将使用 Auth0)来识别用户,所以不需要在每个请求中发送 ClientId(每个用户都属于到某个客户端)...然后,就在 API 访问控制器操作中的上下文之前,它可以修改连接字符串,具体取决于正在执行请求的用户(在访问令牌中)
  • 您应该将其添加为可接受的自我回答,以便对可能遇到相同问题的其他人有所帮助。
  • 谢谢 Nkozi... 我刚刚意识到一个用户实际上可以访问多个数据库(即他可以属于多个客户端)。在这种情况下,登录时,他将不得不选择要连接的数据库(客户端)。我猜前端必须在每个 API 调用中发送一个查询字符串,指定选择的数据库(以便控制器操作知道要在哪个数据库上执行查询)?还是有更好的办法?
  • 你能推荐我在某处设置一个全局变量,这样当用户登录时,他选择了他想连接的客户端(数据库),我记录这个吗?然后来自该用户的所有进一步 API 调用将使用所选数据库(即控制器操作将查询此全局变量以查看此用户正在连接到哪个数据库)
  • 全局变量 = 坏主意。在令牌中存储必要的信息并使用它来指示要访问的数据存储。无需将事情过度复杂化。

标签: rest asp.net-core-mvc asp.net-core-webapi asp.net-core-1.1


【解决方案1】:

@Srivaishnav

我所做的是,每个客户端在每个请求的标头中向 Web API 发送一个tenantId。然后,Web API 在某个中间件中读取这个tenantId,并将其保存在 HttpContext 中,以便在 API 的任何地方都可以读取它。然后,在我的 DbContext 中,在 OnConfiguring 方法中,我从 HttpContext 读取tenantId,并使用它为该 DbContext 创建连接字符串。代码:

TenantIdentifier.cs 中间件:

public class TenantIdentifier
{
    private readonly RequestDelegate _next;
    private readonly IMemoryCache _memoryCache;
    private DbContext _dbContext;        

    public TenantIdentifier(RequestDelegate next, IMemoryCache memoryCache)
    {
        _next = next;
        _memoryCache = memoryCache;
    }

    public async Task Invoke(HttpContext httpContext, DbContext dbContext)
    {
        _dbContext = dbContext;

        string clientIdHeader = httpContext.Request.Headers["clientId"].FirstOrDefault();
        if (string.IsNullOrEmpty(clientIdHeader))
        {
            await _next.Invoke(httpContext);
            return;
        }                

        int clientId = int.Parse(clientIdHeader);

        httpContext.Items["CLIENT"] = clientId;

        await _next.Invoke(httpContext);
    }

public static class TenantIdentifierExtension
{
    public static IApplicationBuilder UseTenantIdentifier(this IApplicationBuilder app)
    {
        app.UseMiddleware<TenantIdentifier>();
        return app;
    }
}

DbContext.cs:

    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenantContext(DbContextOptions<DbContext> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (_httpContextAccessor == null)
            return;

        int? clientId = (int?)_httpContextAccessor.HttpContext.Items["CLIENT"];
        if (!client.HasValue)
        {
            optionsBuilder = null;
            return;
        }

        string connectionString = null;

        switch (tenantId.Value)
        {
            case 1:
                connectionString = "some connection string";
                break;

            case 2:
                connectionString = "some other connection string";
                break;
        }

        optionsBuilder.UseMySql(connectionString);
    }

Startup.cs

    public void Configure(IApplicationBuilder app)
    {
        app.UseTenantIdentifier();
        app.UseMvc();
    }

我已经稍微简化了这个例子,但这就是它的关键所在。如果您有任何问题,请告诉我...祝您好运!

【讨论】:

    【解决方案2】:

    我将从访问令牌中识别出用户(以及她希望使用的数据库)。

    【讨论】:

    • 您能否分享您的代码以供参考。我处于同样的情况,我必须创建一个使用 Web API(我必须创建)的移动应用程序,该应用程序需要连接到 16 个拥有自己数据库的不同客户端。
    • 你好 Srivaishnav。我在上面发布了我的代码。如果您有任何问题,请告诉我。干杯
    猜你喜欢
    • 2018-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-08
    • 2013-05-22
    • 2010-10-11
    • 1970-01-01
    • 2018-11-09
    相关资源
    最近更新 更多