且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

处理asp.net核心Web API中的多个连接字符串(作为参数提供)

更新时间:2022-10-18 15:38:26

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