我正在使用ASP.NET 5和EF7编写一个多租户web服务。存储库数据特定于租户/用户。因为我将在所有调用中使用TenantId,所以我想用当前的TenantId初始化存储库。
public class MyRepository {
private int tenantId;
public MyRepository(MyDbContext context, int tenantId) {
this.tenantId = tenantId;
// ...
}
public Task<List<Data>> GetAllAsync() {
return this.context.Data.Where(d => d.TenantId == this.tenantId).ToListAsync();
}
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
// How do I pass the TenantId to the constructor?
services.AddScoped<MyRepository>();
}
}是否可以在用户通过身份验证后初始化我的存储库一次?如何将TenantId与构造函数一起传递?
发布于 2015-09-21 16:49:43
您的租户和用户id值都是运行时数据,您应该使用not inject runtime data into the components of your system (在本例中是您的存储库),因为这将极大地复杂化您的组合根(正如您已经经历过的那样),并且几乎不可能验证您的对象图。运行时数据应该流经已经构建的对象图。基本上有两种方法可以实现这一点。您可以通过组件的公共API的方法调用传递数据,也可以注入一个组件,该组件负责在请求时检索运行时值。
在您的情况下,后一种选择是最好的。因此,我的建议是注入一个允许检索此上下文信息的组件:
public interface ITenantContext
{
int CurrentTenantId { get; }
}
public interface IUserContext
{
int CurrentUserId { get; }
}
public class MyRepository {
private readonly Func<MyDbContext> contextProvider;
private readonly ITenantContext tentantContext;
public MyRepository(Func<MyDbContext> contextProvider, ITenantContext tentantContext){
this.contextProvider = contextProvider;
this.tentantContex = tentantContex;
}
public Task<List<Data>> GetAllAsync() {
return this.contextProvider().Data
.Where(d => d.TenantId == this.tenantContext.CurrentTenantId)
.ToListAsync();
}这很好地解决了这个问题,因为现在您可以定义一个知道如何检索当前请求的正确承租人ID的ITenantContext实现。例如:
public sealed class AspNetSessionTenantContext : ITenantContext {
public int CurrentTenantId {
get { return (int)HttpContext.Current.Session["tenantId"]; }
}
}这甚至允许您将所有组件注册为singleton,这可以提高性能,并减少常见DI陷阱的更改,如Captive Dependencies。
发布于 2015-09-21 16:30:03
组合根中的配置在应用程序启动时运行一次(每次应用程序池重新启动时运行一次)。注册的类将成为应用程序对象图的一部分,并且在运行时不会更改。
显然,tenantId是应用程序运行时状态的一部分,因此需要在用户请求启动后在运行时注入它。因此,最简单的解决方案是在请求数据时将其作为方法参数传递。
public class MyRepository {
public MyRepository(MyDbContext context) {
this.context = context;
// ...
}
public Task<List<Data>> GetAllAsync(int tenantId) {
return this.context.Data.Where(d => d.TenantId == tenantId).ToListAsync();
}
}通过使用Abstract Factory,您可以只在一个地方传递它。但是,您必须确定这样做的额外复杂性是否真的值得在每次方法调用时避免使用tenantId参数。
public class MyRepositoryFactory : IMyRepositoryFactory
{
private readonly MyDbContext context;
public MyRepositoryFactory(MyDbContext context)
{
this.context = context;
}
public IMyRepository Create(int tenantId)
{
return new MyRepository(this.context, tenantId);
}
}在你的控制器中:
public class MyController : Controller
{
private readonly IMyRepository myRepository
public MyController(IMyRepositoryFactory myRepositoryFactory)
{
// Centralize tenantId logic
int tenantId = GetTenantId();
this.myRepository = myRepositoryFactory.Create(tenantId);
}
public IActionResult Index()
{
// Use the repository
// var x = await this.myRepository.GetAllAsync();
return View();
}
}请注意,因为控制器也是应用程序运行时状态的一部分,所以您可以在其构造函数中查找tenantId,或者(最好是)通过自定义IControllerFactory将其注入控制器中。
在Startup.cs中,您只需注册存储库工厂,如下所示:
services.AddTransient<IMyRepositoryFactory, MyRepositoryFactory>();发布于 2015-09-21 17:09:19
问题是userId是当前的上下文属性。例如,您可以使用User.GetUserId()来获取控制器操作上下文的userId。如果您想通过DI容器传递userId,您应该能够在入口点获得userId。如果可以这样做,可以像这样注册依赖项
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<MyRepository>(() => new MyRepository(GetContext(), GetCurrentUserId()));
}
}但我认为这不是实现GetCurrentUserId()方法的最简单方法。
有一种比在构造函数中传递userId更好的方法。然后,您可以为不同的用户使用相同的存储库实例。
public class IMyRepository
{
public Task<List<Data>> GetAllAsync(int userId);
}
public class MyRepository : IMyRepository
{
private MyDbContext context;
public MyRepository(MyDbContext context) {
this.context = context;
}
public Task<List<Data>> GetAllAsync(int userId) {
return this.context.Data.Where(d => d.UserId== this.userId).ToListAsync();
}
}
public class Startup {
public void ConfigureServices(IServiceCollection services) {
services.AddScoped<IMyRepository, MyRepository>();
}
}您可以从控制器使用它,例如
public class SomeController : Controller
{
private readonly IMyRepository repository;
public SomeController(
IMyRepository repository)
{
this.repository = repository;
}
public async Task<IActionResult> SomeAction()
{
if (User.Identity.IsAuthenticated)
{
var data = await repository.GetAllAsync(User.GetUserId());
// do something else
}
}
}我希望这将是有用的。
https://stackoverflow.com/questions/32685571
复制相似问题