我想在REST Api中在运行时更改与数据库的连接。我想把一个变量放到请求中,让Api决定使用哪个连接字符串。
例如: 我把变量" 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();
}
}
我的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));
}
因此,您可以看到名称为#34; develop&#34;的连接字符串。默认情况下使用。这将返回一个名为&#34; website&#34;的网站。现在我将更改标头变量&#34; dbid&#34;到&#34;生活&#34;。 api应该看到这个,并且应该获得与名称&#34; live&#34;对应的连接字符串。最后一部分是我正在尝试的,但没有任何作用。
我试过了:
这让我抓狂!应该有办法更改请求标头中的值给出的连接字符串,对吗?
答案 0 :(得分: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" };
}
答案 1 :(得分:0)
您需要为动态选择连接字符串创建工厂类。 该类负责根据特定参数提供正确的connectionString。