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

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

我想在运行时在 REST Api 中更改与数据库的连接。我想放置一个请求变量,让 Api 决定使用哪个连接字符串。

例如: 我将值为“develop”的变量“dbid”放在请求 header 中,并将其发送到 Api。

Api 查看 header 并从 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
        };
    }
}

最后但同样重要的是, Controller :

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

如您所见,默认情况下使用名称为“develop”的连接字符串。这将返回一个名为“网站”的网站。现在我将标题变量“dbid”更改为“live”。 api 应该看到这个并且应该得到与名称“live”相对应的连接字符串。最后一部分是我正在尝试的,但没有任何效果。

这个我试过了:

  • 将 session 添加到 webapi。这意味着我打破了 REST api 的无状态思想:未完成
  • 静态也行不通,因为每个人都可以获得相同的连接字符串,但它的用户特定
  • 谷歌,但大多数示例对我不起作用
  • 正在搜索 StackOverflow...请参阅上一点。

这让我发疯!应该有一种方法可以更改由请求 header 中的值给出的连接字符串,对吧?

最佳答案

我在创建的 Multi-Tenancy 应用程序中有相同的场景,我为每个租户使用不同的连接字符串。

您选择的实现并不重要,但您必须确定如何区分每个连接字符串的每个请求。在我的应用程序中,我创建了一个自定义路由值,并在 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 接口(interface)实现(使用路由)。

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" };
}

关于c# - 使用 MVC WebApi 2 在运行时更改数据库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42464645/

相关文章:

c# - 如何在 MVC 2 aspx 中使用 C# 显示和隐藏 Div

c# - 无法反序列化包含 $ref 键的 JSON

c# - Streamwriter 仅从字符串列表中写入一定数量的字节

javascript - Dust.js 逻辑助手的问题

asp.net-mvc - 实体类型 ApplicationUser 不是当前上下文模型的一部分。在项目开始时使用了两个不同的数据库

c# - entityframework 功能说明

c# - Entity Framework 在传递谓词时返回不同的结果

c#变量的可见性

c# - 在 Angular ASP.NET Core 2.0 应用程序中使用 LinkedIn api 和 Oauth 2.0 进行身份验证

ASP.NET MVC与Webforms : Replacing WebForms Controls