【问题标题】:How to pass the UserId/TenantId to a Repository Constructor using Dependency Injection?如何使用依赖注入将 UserId/TenantId 传递给存储库构造函数?
【发布时间】:2015-12-17 14:00:20
【问题描述】:

我正在使用 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?

【问题讨论】:

  • 你可以注入一个只包含租户ID的接口并使用AddInstance方法,像这样:services.AddInstance&lt;ITenant&gt;(GetTenantMethod());甚至services.AddScoped&lt;ITenant&gt;(x =&gt; GetTenantMethod());

标签: asp.net-mvc dependency-injection asp.net-core asp.net-core-mvc entity-framework-core


【解决方案1】:

您可以像这样手动调用构造函数:

services.AddScoped<MyRepository>(s => new MyRepository(context, tenantId));

【讨论】:

  • 我明白这一点,但首先要更改tenantId,其次我需要将其与身份验证集成。
  • 也许您需要考虑重新设计它?尽管它是一个依赖项,但我不确定它是否能与依赖项注入顺利进行。那么使用属性呢?它什么时候改变,tenantId 来自哪里?你能更新你的问题吗?
【解决方案2】:

组合根目录中的配置在应用程序启动时运行一次(每次应用程序池重新启动时运行一次)。注册的类成为应用程序对象图的一部分,在运行时不会更改。

显然,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,你只需要注册repository factory,如下:

services.AddTransient<IMyRepositoryFactory, MyRepositoryFactory>();

【讨论】:

  • 感谢@NightOwl888 的回复。但是我的问题是在 ASP.NET 5 MVC 6 的上下文中,它具有内置的依赖注入(由问题标签指定)。
  • @Martin - 我的答案是针对 ASP.NET 5 MVC 6。你被它的哪一部分绊倒了?
  • 那我一定是错过了什么。你在哪里注册 IMyRepositoryFactory?我希望看到一个 Startup 课程。
  • @Martin - 我用添加到Startup.cs 的行更新了我的答案。抱歉,我以为那部分会很明显。
【解决方案3】:

您的租户和用户 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();
}

这很好地解决了这个问题,因为现在您可以定义一个ITenantContext 实现,它知道如何为当前请求检索正确的租户 ID。例如:

public sealed class AspNetSessionTenantContext : ITenantContext {
    public int CurrentTenantId {
        get { return (int)HttpContext.Current.Session["tenantId"]; }
    }
}

这甚至允许您将所有组件注册为单例,这可以提高性能,并减少常见 DI 陷阱的变化,例如Captive Dependencies

【讨论】:

  • 这是一个很好的答案,但您的示例中的 IUserContext 是什么?
  • @janhartmann:怎么样?问题标题同时涉及租户 ID 和用户 ID,因此我展示了它们的抽象。
  • 啊,好吧,对不起。我只是看不到存储库示例中使用的IUserContext
  • @janhartmann:没错,因为用户 ID 不在 OP 的 MyRepository 示例中。
  • tx,这解决了我的问题!我使用了不同的方法,但使用了您关于注入组件来检索租户 ID 的想法。
【解决方案4】:

问题在于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
            }
    }
}

希望对你有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-20
    • 1970-01-01
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多