使用MVC WebApi 2在运行时更改数据库

时间:2017-02-26 04:08:38

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 asp.net-web-api

我想在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;对应的连接字符串。最后一部分是我正在尝试的,但没有任何作用。

我试过了:

  • 向webapi添加会话。这意味着我打破了REST api的无状态想法:未完成
  • 静态也无法工作,因为每个人都可以获得相同的连接字符串,但其用户特定的
  • Google,但大多数示例都不适合我
  • 搜索StackOverflow ...请参阅上一点。

这让我抓狂!应该有办法更改请求标头中的值给出的连接字符串,对吗?

2 个答案:

答案 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。