ASP.NET Core cambia la cadena de conexión EF cuando el usuario inicia sesión

Después de algunas horas de investigación y de no encontrar la manera de hacerlo; Es hora de hacer la pregunta.

Tengo un proyecto ASP.NET Core 1.1 con EF Core y MVC que utilizan varios clientes. Cada cliente tiene su propia base de datos con el mismo esquema exacto. Este proyecto es actualmente una aplicación de Windows que se está migrando a la web. En la pantalla de inicio de sesión, el usuario tiene tres campos: Código de empresa, Nombre de usuario y Contraseña. Necesito poder cambiar la cadena de conexión cuando el usuario intenta iniciar sesión en función de lo que escribe en la entrada del Código de la empresa y luego recordar su entrada durante toda la sesión.

Encontré algunas formas de hacer esto con una base de datos y un esquema múltiple, pero ninguna con múltiples bases de datos que usan el mismo esquema.

La forma en que resolví este problema no es una solución real al problema, sino una solución que funcionó para mí. Mis bases de datos y mi aplicación están alojadas en Azure. Mi solución a esto fue actualizar mi servicio de aplicaciones a un plan que admite máquinas tragamonedas (solo $ 20 adicionales al mes por 5 máquinas tragamonedas). Cada ranura tiene el mismo programa, pero la variable de entorno que contiene la cadena de conexión es específica de la compañía. De esta manera, también puedo subdominio al que acceden las empresas si lo deseo. Si bien este enfoque puede no ser lo que otros harían, fue el más rentable para mí. Es más fácil publicar en cada ranura que pasar las horas haciendo la otra programación que no funciona correctamente. Hasta que Microsoft facilite cambiar la cadena de conexión, esta es mi solución.

En respuesta a la respuesta de Herzl

Esto parece que podría funcionar. He tratado de implementarlo. Sin embargo, una cosa que estoy haciendo es usar una clase de repositorio que accede a mi contexto. Mis controladores obtienen el repositorio inyectado en ellos para llamar a métodos en el repositorio que acceden al contexto. ¿Cómo hago esto en una clase de repositorio? No hay sobrecarga de OnActionExecuting en mi repositorio. Además, si esto persiste durante la sesión, ¿qué sucede cuando un usuario abre nuevamente su navegador a la aplicación y aún está conectado con una cookie que dura 7 días? ¿No es esta una nueva sesión? Parece que la aplicación arrojaría una excepción porque la variable de sesión sería nula y, por lo tanto, no tendría una cadena de conexión completa. Supongo que también podría almacenarlo como un Reclamo y usar el Reclamo si la variable de sesión es nula.

Aquí está mi clase de repositorio. IDbContextService fue ProgramContext pero comencé a agregar sus sugerencias para intentar que funcione.

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;
    }
}

En respuesta a la respuesta de The Force JB

Traté de implementar tu enfoque. Me sale una excepción en Program.cs en línea

host.Run();

Aquí está mi clase 'Program.cs'. Sin tocar

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();
        }
    }
}

Y mi clase '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, 0, 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" }
                    );
            });
        }
    }
}

Y la excepción:

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]'.

No estoy seguro de qué hacer aquí.

Éxito parcial editar

Bien, tengo tu ejemplo funcionando. Puedo establecer la cadena de conexión en mi constructor de repositorio usando una identificación diferente. Mi problema ahora es iniciar sesión y elegir la base de datos correcta. Pensé en hacer que el repositorio se extrajera de una sesión o reclamo, lo que no fuera nulo. Pero no puedo establecer el valor antes de usar SignInManager en el controlador de inicio de sesión porque SignInManager se inyecta en el controlador que crea un contexto antes de actualizar la variable de sesión. La única forma en que puedo pensar es tener un inicio de sesión de dos páginas. La primera página solicitará el código de la empresa y actualizará la variable de sesión. La segunda página usará SignInManager y tendrá el repositorio inyectado en el constructor de controladores. Esto sucedería después de que la primera página actualice la variable de sesión. En realidad, esto puede ser más atractivo visualmente con animaciones entre ambas vistas de inicio de sesión. A menos que alguien tenga alguna idea sobre cómo hacer esto sin dos vistas de inicio de sesión, intentaré implementar el inicio de sesión de dos páginas y publicar el código si funciona.

En realidad está roto

Cuando funcionaba, es porque todavía tenía una cookie válida. Ejecutaría el proyecto y omitiría el inicio de sesión. Ahora tengo la excepciónInvalidOperationException: No database provider has been configured for this DbContext Después de limpiar mi caché. Lo he revisado todo y el contexto se está creando correctamente. Supongo que Identity está teniendo algún tipo de problema. ¿Podría el siguiente código agregar el marco de la entidad almacena enConfigureServices estar causando el problema?

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, 0, 0); // Cookies last 7 days
})
.AddEntityFrameworkStores<ProgramContext>();

Editar

VerifiquéIdentity es el problema. Saqué datos de mi repositorio antes de ejecutarPasswordSignInAsync y sacó los datos muy bien. ¿Cómo se crea el DbContext para Identity?

Respuestas a la pregunta(1)

Su respuesta a la pregunta