用户登录时,ASP.NET Core更改EF连接字符串

经过几个小时的研究,发现无法做到这一点;是时候提问了.

我有一个使用EF Core和MVC的ASP.NET Core 1.1项目,供多个客户使用.每个客户都有自己的数据库,具有完全相同的模式.该项目目前是一个迁移到Web的Windows应用程序.在登录屏幕上,用户有三个字段,公司代码,用户名和密码.我需要能够在用户尝试根据他们在公司代码输入中键入的内容进行登录时更改连接字符串,然后在整个会话期间记住他们的输入.

我找到了一些方法可以使用一个数据库和多个模式执行此操作,但没有一个使用相同模式的多个数据库.

The way I solved this problem isn’t an actual solution to the problem,but a work around that worked for me. My databases and app are hosted on Azure. My fix to this was to upgrade my app service to a plan that supports slots (only an extra $20 a month for 5 slots). Each slot has the same program but the environment variable that holds the connection string is company specific. This way I can also subdomain each companies access if I want. While this approach may not be what others would do,it was the most cost effective to me. It is easier to publish to each slot than to spend the hours doing the other programming that doesn’t work right. Until Microsoft makes it easy to change the connection string this is my solution.

回应Herzl的回答

这似乎可行.我试图让它实现.我正在做的一件事是使用访问我的上下文的存储库类.我的控制器将注入的存储库注入其中以调用访问上下文的存储库中的方法.我如何在存储库类中执行此操作.我的存储库中没有OnActionExecuting重载.此外,如果会话持续存在,当用户再次向应用程序打开浏览器并且仍然使用持续7天的cookie登录时会发生什么?这不是新会议吗?听起来像应用程序会抛出异常,因为会话变量将为null,因此没有完整的连接字符串.我想我也可以将它存储为一个Claim,如果session变量为null,则使用Claim.

这是我的存储库类. IDbContextService是ProgramContext,但我开始添加你的建议,试着让它工作.

public class ProjectRepository : IProjectRepository
{
    private IDbContextService _context;
    private ILogger<ProjectRepository> _logger;
    private UserManager<ApplicationUser> _userManager;

    public ProjectRepository(IDbContextService context,ILogger<ProjectRepository> logger,UserManager<ApplicationUser> userManger)
    {
        _context = context;
        _logger = logger;
        _userManager = userManger;
    }

    public async Task<bool> SaveChangesAsync()
    {
        return (await _context.SaveChangesAsync()) > 0;
    }
}

回应FORCE JB的回答

我试图实施你的方法.我在Program.cs上得到一个例外

host.Run();

这是我的’Program.cs’课程.不变.

using System.IO;
using Microsoft.AspNetCore.Hosting;

namespace Project
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

还有我的’Startup.cs’课程.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Generic;
using Project.Entities;
using Project.Services;

namespace Project
{
    public class Startup
    {
        private IConfigurationRoot _config;

        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json")
                .AddEnvironmentVariables();

            _config = builder.Build();
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton(_config);
            services.AddIdentity<ApplicationUser,IdentityRole>(config =>
            {
                config.User.RequireUniqueEmail = true;
                config.Password.RequireDigit = true;
                config.Password.RequireLowercase = true;
                config.Password.RequireUppercase = true;
                config.Password.RequireNonAlphanumeric = false;
                config.Password.RequiredLength = 8;
                config.Cookies.ApplicationCookie.LoginPath = "/Auth/Login";
                config.Cookies.ApplicationCookie.ExpireTimeSpan = new TimeSpan(7,0); // Cookies last 7 days
            })
            .AddEntityFrameworkStores<ProjectContext>();
            services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>,AppClaimsPrincipalFactory>();
            services.AddScoped<IProjectRepository,ProjectRepository>();
            services.AddTransient<MiscService>();
            services.AddLogging();
            services.AddMvc()
            .AddJsonOptions(config =>
            {
                config.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            });
        }

        public void Configure(IApplicationBuilder app,ILoggerFactory loggerFactory)
        {
            Dictionary<string,string> connStrs = new Dictionary<string,string>();
            connStrs.Add("company1","1stconnectionstring"));
            connStrs.Add("company2","2ndconnectionstring";
            DbContextFactory.SetDConnectionString(connStrs);
            //app.UseDefaultFiles();

            app.UseStaticFiles();
            app.UseIdentity();
            app.UseMvc(config =>
            {
                config.MapRoute(
                    name: "Default",template: "{controller}/{action}/{id?}",defaults: new { controller = "Auth",action = "Login" }
                    );
            });
        }
    }
}

例外:

InvalidOperationException: Unable to resolve service for type 'Project.Entities.ProjectContext' while attempting to activate 'Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`4[Project.Entities.ApplicationUser,Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole,Project.Entities.ProjectContext,System.String]'.

不知道该怎么做.

部分成功编辑

好的,我让你的例子工作了.我可以使用不同的id在我的存储库构造函数中设置连接字符串.我现在的问题是登录并选择正确的数据库.我想过从会话或声明中获取存储库,无论是非空的.但是我无法在Login控制器中使用SignInManager之前设置该值,因为SignInManager被注入到控制器中,在我更新会话变量之前创建了一个上下文.我能想到的唯一方法是登录两页.第一页将询问公司代码并更新会话变量.第二页将使用SignInManager并将存储库注入到控制器构造函数中.这将在第一页更新会话变量后发生.这实际上可能在两个登录视图之间使用动画更具视觉吸引力.除非有任何想法在没有两个登录视图的情况下执行此操作,否则我将尝试实现两页登录并发布代码(如果有效).

它实际上是破碎的

当它工作时,这是因为我仍然有一个有效的cookie.我会运行该项目,它将跳过登录.现在我收到异常InvalidOperationException:清除缓存后,没有为此DbContext配置数据库提供程序.我已经完成了所有操作并正确创建了上下文.我的猜测是身份存在某种问题.下面的代码添加实体框架存储在ConfigureServices中会导致问题吗?

services.AddIdentity<ApplicationUser,IdentityRole>(config =>
{
    config.User.RequireUniqueEmail = true;
    config.Password.RequireDigit = true;
    config.Password.RequireLowercase = true;
    config.Password.RequireUppercase = true;
    config.Password.RequireNonAlphanumeric = false;
    config.Password.RequiredLength = 8;
    config.Cookies.ApplicationCookie.LoginPath = "/Company/Login";
    config.Cookies.ApplicationCookie.ExpireTimeSpan = new TimeSpan(7,0); // Cookies last 7 days
})
.AddEntityFrameworkStores<ProgramContext>();

编辑

我验证了身份是问题所在.我在执行PasswordSignInAsync之前从我的存储库中提取数据,并且它提取数据就好了.如何为Identity创建DbContext?

解决方法

创建一个DbContext工厂
public static class DbContextFactory
{
    public static Dictionary<string,string> ConnectionStrings { get; set; }

    public static void SetConnectionString(Dictionary<string,string> connStrs)
    {
        ConnectionStrings = connStrs;
    }

    public static MyDbContext Create(string connid)
    {
        if (!string.IsNullOrEmpty(connid))
        {
            var connStr = ConnectionStrings[connid];
            var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
            optionsBuilder.UseSqlServer(connStr);
            return new MyDbContext(optionsBuilder.Options);
        }
        else
        {
            throw new ArgumentNullException("ConnectionId");
        }
    }
}

初始化DbContext工厂

在startup.cs中

public void Configure()
{
  Dictionary<string,string>();
  connStrs.Add("DB1",Configuration["Data:DB1Connection:ConnectionString"]);
  connStrs.Add("DB2",Configuration["Data:DB2Connection:ConnectionString"]);
  DbContextFactory.SetConnectionString(connStrs);
}

用法

var dbContext= DbContextFactory.Create("DB1");

相关文章

在上文中,我介绍了事件驱动型架构的一种简单的实现,并演示...
上文已经介绍了Identity Service的实现过程。今天我们继续,...
最近我为我自己的应用开发框架Apworks设计了一套案例应用程序...
HAL(Hypertext Application Language,超文本应用语言)是一...
在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事...
HAL,全称为Hypertext Application Language,它是一种简单的...