处理asp.net核心Web API中的多个连接字符串(作为参数提供) [英] Handling multiple connection strings in asp.net core web api which came as a parameter
问题描述
在我的一个应用程序中,我遇到了一种情况,用户必须从前端选择要使用的数据库.
然后在每个请求中,由于所有都是web api
调用,因此所选值将作为参数传递,并且需要基于所选数据库进行连接.
因此,当前我正在编写用于在每个action method
中初始化连接的代码.像:-
public async Task<IActionResult> GetData([FromQuery]string Id, [FromQuery]string database)
{
if(database=="A")
connection conn=new connection("connStringA");//dummy code
if(database=="B")
connection conn=new connection("connStringB");//dummy code
// and so on the logic.........
}
我可以创建一个单独的method
,这也会为我做同样的事情,但同样,我确实必须每次都为所有actions
调用该method
.
这里还有一个constructor
和DI
.
我的问题是,在每个action
中都没有writing/calling
的情况下,还有其他更好的方法吗?我相信应该有,但我无法解决.
我正在寻找的东西:-
- 通过
DI
进行注入的任何方式. - 通过
constructor
进行初始化的任何方式. -
action filters
的任何方式. - 或者其他更好的方法.
通过这种方式,数据库选择逻辑与请求分离,并且可以在Web应用程序之外使用(并测试). 另外,如果提供了无效的数据库名称(在操作过滤器中),您还可以选择阻止调用该操作.
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}
public class AppDbContextProvider : IDisposable
{
//enforcing 1 DbContext per request
private Dictionary<string, AppDbContext> _contexts = new Dictionary<string, AppDbContext>();
public AppDbContext GetDbContext(string dbName)
{
if (dbName == null)
return null;
if (_contexts.TryGetValue(dbName, out AppDbContext ctx))
return ctx;
var conStr = GetConnectionString(dbName);
if (conStr == null)
return null;
var dbOptionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
dbOptionsBuilder.UseSqlServer(conStr);
ctx = new AppDbContext(dbOptionsBuilder.Options);
_contexts[dbName] = ctx;
return ctx;
}
//Any connection string selection logic, either hard-coded or configurable somewhere (e.g. Options).
private string GetConnectionString(string dbName)
{
switch (dbName)
{
case "A":
return "a";
case "B":
return "b";
default:
return null;
}
}
//ensure clean dispose after DI scope lifetime
public void Dispose()
{
if (_contexts.Count > 0)
{
foreach (var ctx in _contexts.Values)
ctx.Dispose();
}
}
}
public class PopulateDbContextFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var dbName = filterContext.HttpContext.Request.Query["db"];
var provider = filterContext.HttpContext.RequestServices.GetRequiredService<AppDbContextProvider>();
var ctx= provider.GetDbContext(dbName);
if (ctx == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "Error" }));
}else
{
//could also be stored to any other accessible location (e.g. an controller property)
filterContext.HttpContext.Items["dbContext"] = ctx;
}
base.OnActionExecuting(filterContext);
}
}
然后最后将AppDbContextProvider
作为作用域服务添加到应用程序DI中.
services.AddScoped<AppDbContextProvider>();
这还允许在后台作业或您必须访问多个数据库的情况下使用相同的提供程序. 但是,您不能再通过DI直接注入DbContext.
如果需要迁移,则可能还必须研究设计时DbContext的创建: https://docs.microsoft.com/zh-我们/ef/core/miscellaneous/cli/dbcontext-creation
In one my application I came across in a situation where user has to select the database needs to be used from the front-end.
And then in every request the selected value is passing as a parameter as all are web api
calls and the connection needs to be made based on the selected database.
So currently I am writing the code for initializing the connection in every action method
. like :-
public async Task<IActionResult> GetData([FromQuery]string Id, [FromQuery]string database)
{
if(database=="A")
connection conn=new connection("connStringA");//dummy code
if(database=="B")
connection conn=new connection("connStringB");//dummy code
// and so on the logic.........
}
I can create a separate method
also which will do same thing for me but again I do need have to call that method
in each time for all the actions
.
Here also I have a constructor
with DI
.
My question is is there any other better way of doing this without writing/calling
in each action
. I believe there should be but I am unable to get through it.
What I am looking for like:-
- Any way to have this by injecting through
DI
. - Any way to initialize by
constructor
. - Any way by
action filters
. - Or any better approach.
I recommend creating a DbContextProvider class which has a method that creates a connection string for the db name. Then register it scoped in the DI and use an action filter to populate a property of the Controller.
That way the db selection logic is separated from the request and can be used (and tested) outside of a web application. Also you have the option to prevent the action from being called if an invalid db name is provided (within the action filter).
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}
public class AppDbContextProvider : IDisposable
{
//enforcing 1 DbContext per request
private Dictionary<string, AppDbContext> _contexts = new Dictionary<string, AppDbContext>();
public AppDbContext GetDbContext(string dbName)
{
if (dbName == null)
return null;
if (_contexts.TryGetValue(dbName, out AppDbContext ctx))
return ctx;
var conStr = GetConnectionString(dbName);
if (conStr == null)
return null;
var dbOptionsBuilder = new DbContextOptionsBuilder<AppDbContext>();
dbOptionsBuilder.UseSqlServer(conStr);
ctx = new AppDbContext(dbOptionsBuilder.Options);
_contexts[dbName] = ctx;
return ctx;
}
//Any connection string selection logic, either hard-coded or configurable somewhere (e.g. Options).
private string GetConnectionString(string dbName)
{
switch (dbName)
{
case "A":
return "a";
case "B":
return "b";
default:
return null;
}
}
//ensure clean dispose after DI scope lifetime
public void Dispose()
{
if (_contexts.Count > 0)
{
foreach (var ctx in _contexts.Values)
ctx.Dispose();
}
}
}
public class PopulateDbContextFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var dbName = filterContext.HttpContext.Request.Query["db"];
var provider = filterContext.HttpContext.RequestServices.GetRequiredService<AppDbContextProvider>();
var ctx= provider.GetDbContext(dbName);
if (ctx == null)
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = "Home", action = "Error" }));
}else
{
//could also be stored to any other accessible location (e.g. an controller property)
filterContext.HttpContext.Items["dbContext"] = ctx;
}
base.OnActionExecuting(filterContext);
}
}
Then finally add the AppDbContextProvider
to the application DI as a scoped service.
services.AddScoped<AppDbContextProvider>();
This also allows using the same provider in background jobs or cases where you have to access multiple databases. However you can't directly inject the DbContext by DI anymore.
If you require migrations, you might have to look into Design-time DbContext creation as well: https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation
这篇关于处理asp.net核心Web API中的多个连接字符串(作为参数提供)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!