ユーザーがログインしたときのASP.NETコア変更EF接続文字列

asp.net asp.net-core asp.net-identity asp.net-mvc entity-framework-core

質問

数時間の研究の後、これを行う方法が見つからない場合。それは質問をする時間です。

私は、複数の顧客が使用するEFコアとMVCを使用してASP.NET Core 1.1プロジェクトを持っています。各顧客には、まったく同じスキーマを持つ独自のデータベースがあります。このプロジェクトは現在、Webに移行されているWindowsアプリケーションです。ログイン画面では、会社コード、ユーザー名、パスワードの3つのフィールドがあります。ユーザーが会社コード入力で入力した内容に基づいてログインしようとしたときに接続文字列を変更できるようにする必要があります。

私は1つのデータベースと複数のスキーマでこれを行ういくつかの方法を見つけましたが、同じスキーマを使用する複数のデータベースでは何も見つかりませんでした。

私がこの問題を解決する方法は、問題の実際の解決策ではありませんが、その問題を解決するために私の仕事がありました。私のデータベースとアプリケーションはAzureでホストされています。私がこれを修正したのは、アプリサービスをスロットをサポートするプランにアップグレードすることでした(5スロットの場合、毎月$ 20追加のみ)。各スロットには同じプログラムがありますが、接続文字列を保持する環境変数は会社固有のものです。私が望むならば、この方法で私は各企業のアクセスをサブドメイン化することもできます。このアプローチは、他の人がやることではないかもしれませんが、それは私にとって最も費用効果がありました。各スロットに公開することは、他のプログラミングを行う時間を費やすことよりも簡単です。これは正しく動作しません。マイクロソフトが接続文字列を簡単に変更できるようになるまで、これは私のソリューションです。

ヘルツェルの答えに応じて

これはうまくいくようです。私はそれを実装しようとしました。私がやっていることの1つは、自分のコンテキストにアクセスするリポジトリクラスを使用することです。私のコントローラは、リポジトリに注入されたリポジトリを取得して、コンテキストにアクセスするリポジトリのメソッドを呼び出します。どのように私はリポジトリクラスでこれを行うのですか?私のリポジトリにOnActionExecutingオーバーロードはありません。また、これがセッションに残っている場合、ユーザーがブラウザを開いてアプリケーションに再度アクセスし、7日間続くCookieでログインした場合はどうなりますか?これは新しいセッションではありませんか?セッション変数がnullになり、完全な接続文字列がないため、アプリケーションのようなサウンドは例外をスローします。私は、それをクレームとして保存し、セッション変数がnullの場合にクレームを使用することもできると思います。

ここに私のリポジトリクラスがあります。 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;
    }
}

The 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, 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" }
                    );
            });
        }
    }
}

そして例外:

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を使用して、リポジトリコンストラクタで接続文字列を設定できます。私の問題は、ログインして正しいデータベースを選択することです。私は、リポジトリをセッションやクレームから取り除くことを考えました。何もnullではありませんでした。しかし、セッション変数を更新する前にContInを作成するコントローラにSignInManagerが注入されているため、ログインコントローラでSignInManagerを使用する前に値を設定することはできません。私が考えることができる唯一の方法は、2ページのログインを持つことです。最初のページでは、会社コードを尋ね、セッション変数を更新します。 2番目のページはSignInManagerを使用し、コントローラのコンストラクタにリポジトリを注入します。これは、最初のページがセッション変数を更新した後に発生します。両方のログインビューの間でアニメーションが視覚的に魅力的な場合があります。 2人のログインビューなしで誰かがこれを行うためのアイデアを持っていない限り、私は2ページのログインを実装し、それが動作すればコードを投稿するつもりです。

それは実際に壊れている

それが働いていたとき、私はまだ有効なクッキーを持っていたからです。私はプロジェクトを実行し、ログインをスキップします。今度は例外が発生しますInvalidOperationException: No database provider has been configured for this DbContextキャッシュをクリアした後InvalidOperationException: No database provider has been configured for this 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, 0, 0); // Cookies last 7 days
})
.AddEntityFrameworkStores<ProgramContext>();

編集

Identityが問題であることを確認しました。私はPasswordSignInAsyncを実行する前に自分のリポジトリからデータを取り出して、データをきちんと取得しました。 IDのために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 = new 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");

人気のある回答

あなたの質問によると、私はいくつかのことを仮定した解決策を提供するつもりです:

まず、ローカルのSQL Serverインスタンスに3つのデータベースを作成しました。

create database CompanyFoo
go

create database CompanyBar
go

create database CompanyZaz
go

次に、各データベースに1つの行を持つ1つのテーブルを作成します。

use CompanyFoo
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Foo Company')
go

use CompanyBar
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Bar Company')
go

use CompanyZaz
go

drop table ConfigurationValue
go

create table ConfigurationValue
(
    Id int not null identity(1, 1),
    Name varchar(255) not null,
    [Desc] varchar(max) not null
)
go

insert into ConfigurationValue values ('Company name', 'Zaz Company')
go

次のステップでは、SQL認証を使用してユーザーを作成し、データベースを読み取るためのアクセスを許可します。私のユーザー名はjohndで、パスワードは123です。

これらのステップが完了したら、ASP.NET CoreでMVCアプリケーションを作成し、MultipleCompanyをプロジェクト名として使用しました。ホームと管理の2つのコントローラがあります。まず、ログインビューを表示して別のコントローラにリダイレクトします「ログイン」ビューで選択されたデータベースに従ってデータを表示するビュー。

要件を満たすには、ASP.NET Coreアプリケーションでセッションを使用する必要があります。これは、この方法を記憶域に変更して後でデータを読み取ることができます。これは概念テスト専用です。

HomeControllerコード:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MultipleCompany.Models;

namespace MultipleCompany.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Index(LoginModel model)
        {
            HttpContext.Session.SetString("CompanyCode", model.CompanyCode);
            HttpContext.Session.SetString("UserName", model.UserName);
            HttpContext.Session.SetString("Password", model.Password);

            return RedirectToAction("Index", "Administration");
        }

        public IActionResult Error()
        {
            return View();
        }
    }
}

AdministrationControllerコード:

using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using MultipleCompany.Models;
using MultipleCompany.Services;

namespace MultipleCompany.Controllers
{
    public class AdministrationController : Controller
    {
        protected IDbContextService DbContextService;
        protected CompanyDbContext DbContext;

        public AdministrationController(IDbContextService dbContextService)
        {
            DbContextService = dbContextService;
        }

        public override void OnActionExecuting(ActionExecutingContext context)
        {
            DbContext = DbContextService.CreateCompanyDbContext(HttpContext.Session.CreateLoginModelFromSession());

            base.OnActionExecuting(context);
        }

        public IActionResult Index()
        {
            var model = DbContext.ConfigurationValue.ToList();

            return View(model);
        }
    }
}

ホームビューのコード:

@{
    ViewData["Title"] = "Home Page";
}

<form action="/home" method="post">
    <fieldset>
        <legend>Log in</legend>

        <div>
            <label for="CompanyCode">Company code</label>
            <select name="CompanyCode">
                <option value="CompanyFoo">Foo</option>
                <option value="CompanyBar">Bar</option>
                <option value="CompanyZaz">Zaz</option>
            </select>
        </div>

        <div>
            <label for="UserName">User name</label>
            <input type="text" name="UserName" />
        </div>

        <div>
            <label for="Password">Password</label>
            <input type="password" name="Password" />
        </div>

        <button type="submit">Log in</button>
    </fieldset>
</form>

管理ビューのコード:

@{
    ViewData["Title"] = "Home Page";
}

<h1>Welcome!</h1>

<table class="table">
    <tr>
        <th>Name</th>
        <th>Desc</th>
    </tr>
    @foreach (var item in Model)
    {
        <tr>
            <td>@item.Name</td>
            <td>@item.Desc</td>
        </tr>
    }
</table>

ログインモデルコード:

using System;
using Microsoft.AspNetCore.Http;

namespace MultipleCompany.Models
{
    public class LoginModel
    {
        public String CompanyCode { get; set; }

        public String UserName { get; set; }

        public String Password { get; set; }
    }

    public static class LoginModelExtensions
    {
        public static LoginModel CreateLoginModelFromSession(this ISession session)
        {
            var companyCode = session.GetString("CompanyCode");
            var userName = session.GetString("UserName");
            var password = session.GetString("Password");

            return new LoginModel
            {
                CompanyCode = companyCode,
                UserName = userName,
                Password = password
            };
        }
    }
}

CompanyDbContextコード:

using System;
using Microsoft.EntityFrameworkCore;

namespace MultipleCompany.Models
{
    public class CompanyDbContext : Microsoft.EntityFrameworkCore.DbContext
    {
        public CompanyDbContext(String connectionString)
        {
            ConnectionString = connectionString;
        }

        public String ConnectionString { get; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(ConnectionString);

            base.OnConfiguring(optionsBuilder);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }

        public DbSet<ConfigurationValue> ConfigurationValue { get; set; }
    }
}

ConfigurationValueコード:

using System;

namespace MultipleCompany.Models
{
    public class ConfigurationValue
    {
        public Int32? Id { get; set; }

        public String Name { get; set; }

        public String Desc { get; set; }
    }
}

AppSettingsコード:

using System;

namespace MultipleCompany.Models
{
    public class AppSettings
    {
        public String CompanyConnectionString { get; set; }
    }
}

IDbContextServiceコード:

using MultipleCompany.Models;

namespace MultipleCompany.Services
{
    public interface IDbContextService
    {
        CompanyDbContext CreateCompanyDbContext(LoginModel model);
    }
}

DbContextServiceコード:

using System;
using Microsoft.Extensions.Options;
using MultipleCompany.Models;

namespace MultipleCompany.Services
{
    public class DbContextService : IDbContextService
    {
        public DbContextService(IOptions<AppSettings> appSettings)
        {
            ConnectionString = appSettings.Value.CompanyConnectionString;
        }

        public String ConnectionString { get; }

        public CompanyDbContext CreateCompanyDbContext(LoginModel model)
        {
            var connectionString = ConnectionString.Replace("{database}", model.CompanyCode).Replace("{user id}", model.UserName).Replace("{password}", model.Password);

            var dbContext = new CompanyDbContext(connectionString);

            return dbContext;
        }
    }
}

スタートアップコード:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MultipleCompany.Models;
using MultipleCompany.Services;

namespace MultipleCompany
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc();

            services.AddEntityFrameworkSqlServer().AddDbContext<CompanyDbContext>();

            services.AddScoped<IDbContextService, DbContextService>();

            services.AddDistributedMemoryCache();
            services.AddSession();

            services.AddOptions();

            services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));

            services.AddSingleton<IConfiguration>(Configuration);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseSession();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

私は私のプロジェクトにこのパッケージを追加しました:

"Microsoft.EntityFrameworkCore": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.AspNetCore.Session":  "1.0.0"

私のappsettings.jsonファイル:

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  },
  "AppSettings": {
    "CompanyConnectionString": "server=(local);database={database};user id={user id};password={password}"
  }
}

ホームビューで選択したデータベースに接続するというコンセプトに焦点を当ててください。このコードの一部を改善することができます。私はこの解決策をあなたの簡単な質問に基づいて提供しています。あなたの要件に応じてこのコードを改善するために、このソリューションのあらゆる露出面について

基本的には、選択されたデータベース、つまりIDbContextServiceインターフェイスとDbContextServiceに従って、dbコンテキストのインスタンスを作成するサービスを定義する必要があります。これは、そのインターフェイスの実装です。

DbContextServiceコードでわかるように、{}の中の値を{}内の値に置き換えて別の接続文字列を作成します。この場合、データベース名をドロップダウンリストに追加しましたが、実際の開発では、あなたのデータベースや他の設定の本名を公開しないようにする。選択したデータベースに従って会社コードを解決するために、コントローラの側からパリティテーブルを作成することができます。

この解決策の1つの改善点として、各値を別々の方法で保存するのではなく、ログインモデルをjsonとしてセッションにシリアル化するコードを追加することです。

この回答が役に立つかどうかお知らせください。 PD:フルコードを1つのドライブにアップロードする場合は、コメントでお知らせください



Related

ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ
ライセンスを受けた: CC-BY-SA with attribution
所属していない Stack Overflow
このKBは合法ですか? はい、理由を学ぶ