0

我想在運行時在REST Api中將連接更改爲數據庫。我想放一個請求的變量,讓Api決定使用哪個連接字符串。使用MVC在運行時更改數據庫WebApi 2

例如: 我在請求標題中放置值爲「develop」的變量「dbid」併發送給Api。

Api看到標題並從web.config中獲取正確的連接字符串。

我有三層(數據,業務,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; 
    } 
} 

在業務我有一個類來檢索數據層數據,並做一些邏輯的東西(沒有必要在這裏,但對於尚好故事)。

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

我的統一配置:

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):

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

而不是你的你的執行中的DbContext,我會改變是這個,然後總是通過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中添加其他連接字符串值,並使用「name」而不是從web.config中複製的格式化連接字符串引用它們。 –

+0

是的,這是有效的。現在看,它看起來很簡單。我完全錯過了HttpContext.Current.Request.RequestContext.RouteData(對於我的請求標題),所以我無法弄清楚如何從請求中獲取信息並將其放入正確的變量中。 。在構造函數中這樣做。那麼,另一件事情學到了。感謝您的完美解釋和幫助 –

0

您需要爲動態選取連接字符串創建一個工廠類。 該類的職責是根據某個參數給出正確的connectionString。

相關問題