我正在构建一个使用服务/存储库模式w / Entity Framework的.Net Web Api。下面是一个控制器,其中包含与服务接口的CRUD操作:
public class SomeController : BaseApiController
{
private IService _service;
public SomeController(IService _service)
{
_service = service;
}
public object Get() { return _service.GetItems(); }
...
}
我想使用Microsoft Unity IoC将数据库上下文注入到下面的服务构造函数中:
// Service implements IService
public Service(SomeContext ctx) : base(ctx)
{
_alpha = new AlphaRepository(ctx);
_bravo = new BravoRepository(ctx);
}
这对我来说非常适用于单个静态DbContext。但是,Api必须使用动态DbContext b / c,在发出请求并且通过查询字符串传递各种配置数据之前,服务器和数据库是未知的。 ?客户=客户端和放大器;属性=属性。每个客户端都有自己的数据库,每个数据库都位于两个服务器之一。
有一个内部开发的NuGet包,带有暴露的ContextFactory,在发出请求时可以调用它来检索适当的DbContext:
ContextFactory.GetSomeContext(client, prop);
首先,我想在BaseController上使用ActionFilter来解析客户端和属性键的HTTPActionContext请求的查询字符串。使用此信息,可以检索上下文并使用Unity容器注册:
// Belongs to 'public class SomeFilterAttribute : ActionFilterAttribute'
public override void OnActionExecuting(HttpActionContext actionContext)
{
var someContext = ContextFactory.GetSomeContext(client, prop);
private IUnityContainer _unityContainer;
_unityContainer = (IUnityContainer)actionContext.Request.GetDependencyScope().GetService(typeof(IUnityContainer));
_unityContainer.RegisterInstance<SomeContext>(someContext, new PerThreadLifetimeManager());
...
}
当我意识到在SomeController初始化之后执行ActionFilter并且因此它的构造函数和Service构造函数已经完成执行并且注册DbContext为时已晚时,这个计划失败了。
问题: 检索动态DbContext并使用Unity将此实例注入Service构造函数的最佳方法是什么?
我读过有关使用委托的内容,例如
public delegate ISomeContext CreateSomeContext(string client, string prop);
然后在UnityConfig.cs RegisterComponents()
中container.RegisterInstance<CreateSomeContext>((c, p) => ContextFactory.GetSomeContext(c, p));
但我不确定如何在运行时从查询字符串中实际提供客户端和属性。
感谢您的帮助!
答案 0 :(得分:1)
这听起来像是要使用每个租户数据库的多租户结构。首先要避免在查询字符串上传递租户标识符(ClientID)。相反,我建议您阅读OAuth和Owin,以在身份验证令牌中嵌入客户端标识(甚至是连接字符串)。我在我的应用程序中使用Db-per-tenant结构,大致如下:
用户访问网站并定向登录。
身份验证服务与管理所有租户的中央数据库进行通信。此DB充当集线器,用于控制存储租户的服务器/数据库及其数据库版本。 (在升级过渡期间定向到版本化的应用服务器)
成功验证后,OAuth令牌将加载租户ID,架构名称(DB名称)和连接字符串。它存储为加密的声明。
数据服务器请求使用策略模式检索Owin会话上下文以检索令牌,并根据身份验证服务对其进行验证。一个例子:
TenantConnectionModel ITenantIdentityStrategy.RetrieveTenant()
{
if (_tenant != null) // Cached copy...
return _tenant;
var context = HttpContext.Current.GetOwinContext();
var eMailAddress = context.Authentication.User.Identity.Name;
var tenantIdClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.TenantId");
var schemaClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.DBName");
var connectionStringClaim = context.Authentication.User.Claims.SingleOrDefault(x => x.Type == "YourNamespace.ConnectionString");
if (tenantIdClaim == null || schemaClaim == null || connectionStringClaim == null)
return null;
// TODO example: Call the auth service here with the e-mail address and tenant ID
// to validate that the current user has logged in and session hasn't timed
// out.
_tenant = new TenantConnectionModel
{
TenantId = long.Parse(tenantIdClaim.Value),
SchemaName = schemaClaim.Value,
ConnectionString = connectionStringClaim.Value,
};
return _tenant;
}
成功验证后,它会提取租户详细信息,包括连接字符串。
IOC Container在构造DbContexts时使用委托方法,DbContexts检索租户标识策略(第3部分)并获取要提供给上下文的连接字符串。该策略可以在解析DbContexts时缓存多个调用的租户详细信息,它的生命周期范围应该是每个请求。
我通常使用DbContextScope工作模式单元,并对其进行了调整,以适应每个租户或每个租户架构环境的多租户环境。随意看看@ https://github.com/StevePy/DbContextScope