根据提供的信息,我将使用相同的控制器并查找连接字符串,而不是为每个客户端托管单独的 Web API 实例。托管多个实例会更加复杂,并且所指出的唯一区别是连接字符串,我认为这种复杂性是不合理的。
我们需要做的第一件事是确定哪个客户端正在调用以获得适当的连接字符串。这可以通过令牌、标头、请求数据或路由来完成。路由是最简单的,也是客户端最容易访问的,所以我将演示如何使用它;但是,在决定如何做出决定时,请仔细考虑您的要求。
[Route( "{clientId}" )]
public Foo Get( string clientId ) { /* ... */ }
接下来我们需要为客户端获取正确的DbContext。我们想继续使用 DI,但这很复杂,因为直到创建 Controller 之后我们才知道构造对象需要什么连接字符串。因此,我们需要注入某种形式的工厂而不是对象本身。在这种情况下,我们将其表示为Func<string, IUnitOfWork>,并理解它将“clientId”作为字符串并返回适当实例化的IUnitOfWork。我们也可以为此使用命名接口。
[RoutePrefix("foo")]
public class FooController : ApiController
{
private Func<string, IUnitOfWork> unitOfWorkFactory;
public FooController( Func<string, IUnitOfWork> unitOfWorkFactory )
{
this.unitOfWorkFactory = unitOfWorkFactory;
}
[Route( "{clientId}" )]
public Foo Get( string clientId )
{
var unitOfWork = unitOfWorkFactory(clientId);
// ...
}
}
剩下的就是配置我们的依赖注入容器来为我们提供Func<string, IUnitOfWork>。这在实施之间可能会有很大差异。以下是在 Autofac 中执行此操作的一种可能方法。
protected override void Load( ContainerBuilder builder )
{
// It is expected `MyDbContext` has a constructor that takes the connection string as a parameter
// This registration may need to be tweaked depending on what other constructors you have.
builder.Register<MyDbContext>().ForType<DbContext>().InstancePerRequest();
// It is expected `UnitOfWork`'s constructor takes a `DbContext` as a parameter
builder.RegisterType<UnitOfWork>().ForType<IUnitOfWork>().InstancePerRequest();
builder.Register<Func<string, Bar>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, DbContext>>();
var unitOfWorkFactory = c.Resolve<Func<DbContext, IUnitOfWork>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
return unitOfWorkFactory(dbContextFactory(connectionString));
};
});
}
使用 Autofac 是因为 cmets 指示当前正在使用 Autofac,尽管使用其他容器可能会产生类似的结果。
这样,控制器应该能够被实例化,并且每个请求都将使用适当的连接字符串。
基于链接项目的示例注册:
builder.Register<Func<string, IEmployeeService>>(
c =>
{
var dbContextFactory = c.Resolve<Func<string, IMainContext>>();
var unitOfWorkFactory = c.Resolve<Func<IMainContext, IUnitOfWork>>();
var repositoryFactory = c.Resolve<Func<IMainContext, IEmployeeRepository>>();
var serviceFactory = c.Resolve<Func<IUnitOfWork, IEmployeeService>>();
return clientId =>
{
// You may have injected another type to help with this
var connectionString = GetConnectionStringForClient(clientId);
IMainContext dbContext = dbContextFactory(connectionString);
IUnitOfWork unitOfWork = unitOfWorkFactory(dbContext);
IEmployeeRepository employeeRepository = repositoryFactory(dbContext);
unitOfWork.employeeRepositoty = employeeRepository;
return serviceFactory(unitOfWork);
};
});
如果您发现注册变得过于繁琐,因为需要手动进行一些接线,您可能需要在确定客户端后查看更新(或创建新)容器,以便您可以更多地依赖容器.