简体   繁体   中英

ASP Core 3.1 multiple/alterante DB contexts

I have a question, about how to set up a project for multiple clients. We currently develop an ASP Core application. All clients use the same database structure, so they have identical databases, just filled with different users,... We currently use one publish, set up two test websites on IIS and each of those publishes has a different JSON config file containing the DB context (read out at Startp.cs).

Our problem here is, that if we have multiple clients, we have to copy our publish multiple times to maintain multiple websites for the IIS. We didn't find a way to just use one publish and define the config file to be used dependent on the URL/port of the connected client. We tried a tutorial for tenancy under ASP Core, but it failed at the User Manager.

Can someone point me out, to what's the best solution for this kind of webproject, which requires one shared website/publish under IIS, different DB contexts for each client (URL/port), user authentication (we used Identity for that). We tried this for weeks now, but failed to get it working. Our main problem is, that the DB context is created in the Startup.cs before the host is built, so we don't have any URL's from the request and can't create a specific DB context when the application starts. This means, we can't fill the UserStore and can't get the login to work. We know we could make a master table with all the users and domains, but we really would like to avoid this approach, as we are not sure, if the websites will run on the same server, or if one client may use his own internal company server.

EDIT: I need to be more specific, as I made a small mistake. The databases are identical. So i actually won't need different context classes, but just different connection strings. But they are set in the Startup.cs in the AddDbContext function...

EDIT2: I tried a few solutions now, and I'm always failing on Identity. It doesn't seem to work and crashes, when no DbContext is created directly during Startup.

EDIT3: As I failed so far, here are some Code Snippets from our Projekt

In our Startup.cs/ConfigureServices(IServiceCollection services)

  • We are loading our DB settings from a config file
    IConfigurationSection DbSection = Configuration.GetSection("Database");
  • We are adding the DB Context
    services.AddDbContext<MyDbContext>(options => options.UseSqlServer(MyDbContext.createConnectionString(DbSection), b => b.MigrationsAssembly("MyNamespace.DB").EnableRetryOnFailure()));
  • And setting up Identity
    services.AddScoped<Microsoft.AspNetCore.Identity.SignInManager<MyUser>, MySignInManager>();
    services.AddScoped<Microsoft.AspNetCore.Identity.IUserStore<MyUser>, MyUserStore>();
    services.AddScoped<Microsoft.AspNetCore.Identity.UserManager<MyUser>, Services.LogIn.MyUserManager>();

    services.AddIdentity<MyUser, MyRole>()
                    .AddUserManager<MyUserManager>()
                    .AddUserStore<MyUserStore>()
                    .AddRoleStore<MyRoleStore>()
                    .AddDefaultTokenProviders();
  • We are adding authorization
    services.AddAuthorization(options =>
        { options.DefaultPolicy = new AuthorizationPolicyBuilder()
                      .RequireAuthenticatedUser()
                      .Build();
        });

In our Startup.cs/Configure(...) - We set UseAuthorization

    app.UseAuthentication();
    app.UseAuthorization();

In our MyDbContext class we are creating the connection string

     public static string createConnectionString(IConfigurationSection configurationSection)
            {
                return @"server=" + configurationSection.GetSection("Server").Value +
                       ";database=" + configurationSection.GetSection("Database").Value +
                       ";persist security info=True;user id=" + configurationSection.GetSection("User").Value +
                       ";password=" + configurationSection.GetSection("Password").Value + ";multipleactiveresultsets=True;";

            }

So basically all we are trying to achieve, is to make different connections for different urls. Let's say we have 2 cients:

clientone.mysite.com:123 -> database_clientone
clienttwo.mysite.com:456 -> database_clienttwo

This does not work, as we don't have the domain when creating the DbContext. But we are required to have each database store it's own login credentials, instead of using a master table with all the logins for each existing users/databases...

And Identity doesn't seem to work with this scenario either.

You can have multiple appsettings.json files and depending on client load appropriate json file. With above case you will still be creating Db Context in Startup.cs

public class ConfigurationService : IConfigurationService
    {


        public IConfiguration GetConfiguration()
        {
            var env = GetCurrentClient();//You can also set environment variable for client

            CurrentDirectory = CurrentDirectory ?? Directory.GetCurrentDirectory();
            return new ConfigurationBuilder()
                .SetBasePath(CurrentDirectory)
                  .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
                 .AddJsonFile($"appsettings.{env}.json", optional: true, reloadOnChange: false)

                .AddEnvironmentVariables()
                .Build();



        }
    }

If you are registering your DB context with Core's DI via AddDbContext call you can leverage it's overload which provides you access to IServiceProvider :

public void ConfigureServices(IServiceCollection services)
       {
           services
               .AddEntityFrameworkSqlServer()
               .AddDbContext<MyContext>((serviceProvider, options) =>
               {
                    var connectionString = serviceProvider.GetService // get your service 
                        // which can determine needed connection string based on your logic
                         .GetConnectionString();
                    options.UseSqlServer(connectionString);
               });
       }

Also here something similar is achieved with Autofac.

Instead of using AddDbContext, try to use inject DBContext lazily. Add a class DataContext which will implement DBContext

public class DataContext : DbContext
    {

        public DataContext(IConfigurationService configService)
            : base(GetOptions( GetConnectionString())
        {
            this.ChangeTracker.LazyLoadingEnabled = true;
        }


        private static DbContextOptions GetOptions(string connectionString)
        {

            return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), connectionString).Options;
        }


      public static string GetConnectionString()
        {
           //Your logic to read connection string by host
           // This method will be called while resolving your repository
        }

    }

In your Startup.cs remove AddDbContext() call and instead use

  public void ConfigureServices(IServiceCollection serviceCollection)
        {
            serviceCollection.AddScoped<DbContext, YourDBContext>();
//likewise Addscope for your repositories and other services
//Code to build your host
}

DI DbContext in your controller's constructor or in service's constructor

Since your DBContext is now being loaded lazily, you will be able decide on connection string depending on your host.

Hope this works for you!!

I tried to create an environment variable via web.config and reading it out within our application. But I couldn't find a way, how to setup multiple websites in IIS7 with the SAME publish/file-folder, but DIFFERENT web.config files. This didn't work either, or at least I had no clue how to achieve this...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM