处理asp.net核心Web API中的多个连接字符串(作为参数提供) [英] Handling multiple connection strings in asp.net core web api which came as a parameter

查看:122
本文介绍了处理asp.net核心Web API中的多个连接字符串(作为参数提供)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的一个应用程序中,我遇到了一种情况,用户必须从前端选择要使用的数据库.

然后在每个请求中,由于所有都是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.

这里还有一个constructorDI.

我的问题是,在每个action中都没有writing/calling的情况下,还有其他更好的方法吗?我相信应该有,但我无法解决.

我正在寻找的东西:-

  • 通过DI进行注入的任何方式.
  • 通过constructor进行初始化的任何方式.
  • action filters的任何方式.
  • 或者其他更好的方法.
我建议创建一个DbContextProvider类,该类具有为数据库名称创建连接字符串的方法. 然后将其注册到DI中,并使用操作过滤器填充Controller的属性.

通过这种方式,数据库选择逻辑与请求分离,并且可以在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屋!

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