如何根据客户端调用实现具有多个数据库的 Web API
How to implement Web API with many databases depending on client calling
我必须为一个系统创建一个 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 参数。并且基于此参数,该操作会在调用它之前即时修改 Context 的连接字符串?不过,这听起来很糟糕——无论是从实现还是安全的角度来看。问题是,据我所知,ASP.NET 是无状态的,所以每次调用 API 时,API 都不知道这是谁?
或者我还能怎么做?
我将从访问令牌中识别用户(以及她希望使用的数据库)。
@Srivaishnav
我所做的是,每个客户端在每个请求的 header 中向 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();
}
我已经稍微简化了这个例子,但这几乎就是它的症结所在。如果您有任何问题,请告诉我...祝您好运!
我必须为一个系统创建一个 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 参数。并且基于此参数,该操作会在调用它之前即时修改 Context 的连接字符串?不过,这听起来很糟糕——无论是从实现还是安全的角度来看。问题是,据我所知,ASP.NET 是无状态的,所以每次调用 API 时,API 都不知道这是谁?
或者我还能怎么做?
我将从访问令牌中识别用户(以及她希望使用的数据库)。
@Srivaishnav
我所做的是,每个客户端在每个请求的 header 中向 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();
}
我已经稍微简化了这个例子,但这几乎就是它的症结所在。如果您有任何问题,请告诉我...祝您好运!