0

私は、REST APIの実行時にデータベースへの接続を変更したいです。私はリクエストの変数を入れて、Apiがどの接続文字列を使うかを決めさせたい。MVC WEBAPI 2で実行時にデータベースを変更する

例: 私は変数 "dbid"を "開発"という値でリクエストヘッダに入れ、それをApiに送信します。

APIはヘッダを見て、web.configファイルから正しいれたconnectionStringを取得します。

私は三つの層(データ、ビジネス、API)を持っています。データには、データを取得および設定するためのEntityFrameworkが含まれています。このように:ビジネスで

public class WebsiteContext : IocDbContext, IWebsites 
{ 
    public DbSet<Website> Websites { get; set; } 

    public IEnumerable<Website> GetAll() 
    { 
     return Websites.ToList(); 
    } 
} 

(IoCDbContext.cs)

public class IocDbContext : DbContext, IDbContext 
{ 
    public IocDbContext() : base("develop") 
    { 
    } 

    public void ChangeDatabase(string connectionString) 
    { 
     Database.Connection.ConnectionString= connectionString; 
    } 
} 

私はdatalayerからデータを取得し、ためにいくつかの論理ここで必要とされないもの(が、それでも良いを行うためのクラスを持っていますストーリー)。

public class Websites : IWebsites 
{ 
    private readonly Data.Interfaces.IWebsites _websiteContext; 

    #region Constructor 

    public Websites(Data.Interfaces.IWebsites websiteContext) 
    { 
     _websiteContext = websiteContext; 
    } 

    #endregion 

    #region IWebsites implementation 


    public IEnumerable<Website> GetWebsites() 
    { 
     List<Data.Objects.Website> websiteDtos = _websiteContext.GetAll().ToList(); 

     return websiteDtos.Select(web => web.ToModel()).ToList(); 
    } 

    #endregion 
} 

public static class WebsiteMapper 
{ 
    public static Website ToModel(this Data.Objects.Website value) 
    { 
     if (value == null) 
      return null; 

     return new Website 
     { 
      Id = value.Id, 
      Name = value.Name 
     }; 
    } 
} 

そして、少なくとも最後のではなく、コントローラ:

public class WebsiteController : ApiController 
{ 
    private readonly IWebsites _websites; 

    public WebsiteController(IWebsites websites) 
    { 
     _websites = websites; 
    } 

    public IEnumerable<Website> GetAll() 
    { 
     return _websites.GetWebsites().ToList(); 
    } 
} 

マイUnityコンフィギュレーション:

public static void RegisterComponents() 
    { 
     var container = new UnityContainer(); 

     container.RegisterType<Business.Interfaces.IWebsites, Websites>(); 

     container.RegisterType<IDbContext, IocDbContext>(); 
     container.RegisterType<IWebsites, WebsiteContext>(); 

     // e.g. container.RegisterType<ITestService, TestService>(); 

     GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container); 
     DependencyResolver.SetResolver(new UnityDependencyResolver(container)); 
    } 

あなたが名前の接続文字列を見ることができるように "発展" でありますデフォルトで使用されます。これにより、「ウェブサイト」という名前のウェブサイトが返されます。今私はヘッダ変数 "dbid"を "live"に変更します。 APIはこれを見て、 "live"という名前に対応する接続​​文字列を取得する必要があります。この最後の部分は私が試しているものですが、何も動作しません。

この私が試した:

  • をWEBAPIにセッションを追加します。これは私がREST APIをステートレスなアイデアを破る意味:
  • 行われていない静力学はどちらか動作しないことができ、誰もが同じたconnectionStringを取得する可能性があるため、そのユーザは、特定の
  • グーグルが、例のほとんどは私のために動作しません
  • StackOverflowのの検索
  • ...前のポイントを参照してください。

これは狂気私を運転しています!リクエストヘッダーの値によって与えられる接続文字列を変更する方法が必要です。

答えて

2

私はテナントごとに異なる接続文字列を使用するマルチテナントアプリケーションで同じシナリオを作成しました。

選択する実装は問題ありませんが、接続文字列ごとに各要求をどのように区別するかを決定する必要があります。私のアプリケーションでは、カスタムルート値を作成し、それをurlで使用して各リクエストを区別しました。重要なのは、この仕組みが何であれ作成することです。DIフレームワークでは、リクエストごとに登録する必要があります。(Ninjectを使用して)例えば

:むしろ、あなたのDbContextの実装よりも

private static void RegisterServicdes(IKernel kernel) 
{ 
    kernel.Bind<ISiteContext>().To<SiteContext>().InRequestScope(); 
    kernel.Bind<IDbContextFactory>().To<DbContextFactory>().InRequestScope(); 
    // register other services... 
} 

、私はこの後、常にDbContextFactory経由でDbContextインスタンスを作成する変更します。

public class IocDbContext : DbContext, IDbContext 
{ 
    public IocDbContext(string connectionStringType) : base(connectionStringType) { } 
} 

次に、あなたがあなたのDbContextを作成するときに使用するDbContextFactoryを作成し、依存関係として上記のクラスを取る必要があります。あるいは、依存関係をサービスに取り込み、代わりにDbContextFactoryに渡すこともできます。

public interface IDbContextFactory 
{ 
    TestModel CreateContext(); 
} 

public class DbContextFactory : IDbContextFactory 
{ 
    private string _siteType; 
    public DbContextFactory(ISiteContext siteContext) 
    { 
     _siteType = siteContext.Tenant; 
    } 

    public TestModel CreateContext() 
    { 
     return new TestModel(FormatConnectionStringBySiteType(_siteType)); 
    } 

    // or you can use this if you pass the IMultiTenantHelper dependency into your service 
    public static TestModel CreateContext(string siteName) 
    { 
     return new TestModel(FormatConnectionStringBySiteType(siteName)); 
    } 

    private static string FormatConnectionStringBySiteType(string siteType) 
    { 
     // format from web.config 
     string newConnectionString = @"data source={0};initial catalog={1};integrated security=True;MultipleActiveResultSets=True;App=EntityFramework"; 

     if (siteType.Equals("a")) 
     { 
      return String.Format(newConnectionString, @"(LocalDb)\MSSQLLocalDB", "DbOne"); 
     } 
     else 
     { 
      return String.Format(newConnectionString, @"(LocalDb)\MSSQLLocalDB", "DbTwo"); 
     } 
    } 
} 

あなたDbContextにアクセスするとき、あなたがそうのようにそれを使用することができます:

public class DbAccess 
{ 
    private IDbContextFactory _dbContextFactory; 
    public DbAccess(IDbContextFactory dbContextFactory) 
    { 
     _dbContextFactory = dbContextFactory; 
    } 

    public void DoWork() 
    { 
     using (IocDbContext db = _dbContextFactory.CreateContext()) 
     { 
      // use EF here... 
     } 
    } 
} 

ISiteContext(ルートを利用するための)インターフェースの実装。

public interface ISiteContext 
{ 
    string Tenant { get; } 
} 

public class SiteContext : ISiteContext 
{ 
    private const string _routeId = "tenantId"; 

    private string _tenant; 
    public string Tenant { get { return _tenant; } } 

    public SiteContext() 
    { 
     _tenant = GetTenantViaRoute(); 
    } 

    private string GetTenantViaRoute() 
    { 
     var routedata = HttpContext.Current.Request.RequestContext.RouteData; 

     // Default Routing 
     if (routedata.Values[_routeId] != null) 
     { 
      return routedata.Values[_routeId].ToString().ToLower(); 
     } 

     // Attribute Routing 
     if (routedata.Values.ContainsKey("MS_SubRoutes")) 
     { 
      var msSubRoutes = routedata.Values["MS_SubRoutes"] as IEnumerable<IHttpRouteData>; 
      if (msSubRoutes != null && msSubRoutes.Any()) 
      { 
       var subRoute = msSubRoutes.FirstOrDefault(); 
       if (subRoute != null && subRoute.Values.ContainsKey(_routeId)) 
       { 
        return (string)subRoute.Values 
         .Where(x => x.Key.Equals(_routeId)) 
         .Select(x => x.Value) 
         .Single(); 
       } 
      } 
     } 

     return string.Empty; 
    } 
} 

APIアクション:

[Route("api/{tenantId}/Values/Get")] 
[HttpGet] 
public IEnumerable<string> Get() 
{ 

    _testService.DoDatabaseWork(); 

    return new string[] { "value1", "value2" }; 
} 
+0

だから、私はまっすぐに、これを取得してみましょう:ISiteContexではどの接続文字列を使用するかを決定ロジックのですか? (siteContext.SiteTypeに記述されているように)。もしそうなら、あなたは私を助けて、そのインターフェースの実装を追加できますか? –

+0

実装コードをいくつか追加しました。それが役に立ったら教えてください。ルートの代わりにヘッダーを使用するように簡単に変更することも、web.configに接続文字列の値を追加して、web.configからコピーした書式設定された接続文字列の代わりに「名前」を使用して参照することもできます。 –

+0

うん、それは動作します。それを見ると、それはとても簡単で簡単に見えるようになります。私は完全にHttpContext.Current.Request.RequestContext.RouteData(リクエストのヘッダー)を見逃してしまったので、リクエストから情報を取得して正しい変数に入れる方法を理解できませんでした。そしてコンストラクタでそうすることによって。まあ、別のことが学んだ。あなたの完璧な説明と助けをありがとう –

0

あなたは、接続文字列の動的なピッキングのためのファクトリクラスを作成する必要があります。 特定のパラメータに基づいて正しいconnectionStringを与えることは、そのクラスの役割です。

関連する問題