【问题标题】:Dependency injection : how do i resolve multiple instances at runtime?依赖注入:如何在运行时解析多个实例?
【发布时间】:2017-04-17 09:34:25
【问题描述】:

我使用Simple Injector 来享受注射乐趣。

据我所知,我只在我的引导程序项目中注册和解析我的容器。

所以我的控制台应用程序中有这段代码:

static void Main(string[] args)
{
    Container container = ComposeRoot();

    var controller = container.GetInstance<IBussinesLogicController>();
    controller.Execute();

    Console.WriteLine("Started");
    Console.ReadLine();
}

private static Container ComposeRoot()
{
    var container = new Container();

    //Bussines Logic controllers.
    container.Register<IBussinesLogicController, BussinesLogicController>(Lifestyle.Singleton);
    container.Register<IStationController, StationController>(Lifestyle.Transient);

    //Data Access controllers.
    container.Register<IDataAccessController, DataAccessController>(Lifestyle.Singleton);
    container.Register<IDirectoryStructureController, DirectoryStructureController>(Lifestyle.Singleton);

    //Shared controllers.
    container.Register<ILogger, LoggerController>(Lifestyle.Singleton);

    container.Verify();

    return container;
}

这将解析我的业务逻辑控制器并调用 Execute。

现在在执行中,我想解析 IStationController 的 10 个新实例并将它们添加到列表中。

var stations = new List<IStationController>();
for (int i = 0; i < 10; i++)
{
    //Resolve IStationController here...
    //IStationController station = diContainer.GetInstance<IStationController>();
    //station.StationName = $"Station {i}";
    //stations.Add(station);
}

我怎样才能正确地做到这一点,因为我的业务逻辑项目不允许也不允许引用依赖容器?

我可以通过将依赖项添加到容器来轻松解决此问题,但恐怕我没有遵循正确的模式。

我的解决方案布局如下:

  • 类库 (DAL)
  • 类库 (BLL)
  • 类库(模型)
  • 类库(记录器)
  • 控制台应用程序 (ConsoleApp6)

请让我知道解决此问题的正确方法是什么。

谢谢!

【问题讨论】:

  • 您的业务逻辑不能将引用调用为 DI 容器,重要的是要了解业务或其他层需要 StationController 的位置。您可以创建一个工厂,它在内部调用 DI 容器
  • 嗨 Mrinal Kamboj 好的,但是这个工厂将再次出现在我的 bussineslogic 实现中,并且不会引用 DI 容器,您能否举例说明您的意思。此时只有引导应用程序(控制台应用程序)对 DI 容器有引用。
  • 检查我的答案,只有Factory有对DI容器的引用,没有调用者/业务层/控制台应用程序
  • StationController 是什么类型的对象?我假设包含应用程序行为(因为您在控制器中注册了它),但我注意到它包含一个名为 StationName 的可变属性,这对于组件来说很奇怪。组件通常应该是不可变的,它们不应该需要runtime data (a name) during construction。您能否详细说明StationController 是什么,为什么它有名字,以及为什么要创建它们的列表?它是一个组件,还是其他东西,例如实体或 DTO?
  • @Steven 请注意,所有这些都是用于学习 Simple Injector 框架的测试代码,详细说明,StationController 是一个控制器,它将读取某个目录并使用(解析)任何 xml里面的文件,你如何将这个站名信息注入这个类?

标签: c# design-patterns dependency-injection inversion-of-control ioc-container


【解决方案1】:

您可以注册一个factory delegate 并在您的课堂上使用它。

如下注册代理:

container.Register<IStationController, StationController>(Lifestyle.Transient);
container.RegisterSingleton<Func<IStationController>>(
    () => container.GetInstance<IStationController>());

并将其注入您的 BussinesLogicController 构造函数:

public BussinesLogicController (Func<IStationController> stationControllerFacrtory)

然后你在循环中使用它:

List<IStationController> stations = new List<IStationController>();
for (int i = 0; i < 10; i++)
{
    //Resolve IStationController here...
    IStationController station = this.stationControllerFactory.Invoke();
    station.StationName = $"Station {i}";
    stations.Add(station);
}   

【讨论】:

  • 这仍然需要 BussinesLogicController 的调用者引用 DI 容器,因为注入了 GetInstance,我不确定这对 OP 的要求有多大作用,但相当不错接受委托并调用以获取对象的方法
  • @MrinalKamboj,这不是真的,这是由容器自动完成的。无需注入容器。
  • 嗨@OfirWinegarten,这可以在没有引用DI容器的情况下工作,但这是问题的最终解决方案还是只是让它工作的黑客?这是您在生产代码中解决此问题的方式吗?感谢您的回答。
  • 感谢@OfirWinegarten 我已经接受了这个答案,因为它确实提供了一种灵活的方式来为我的 DI 解析实例创建工厂,也感谢 MrinalKamboj 对我的问题的贡献。
  • 酷 +1 我喜欢这种方法,虽然你链接的页面说最好避免这种策略,因为它有一种设计味道,它可能服务于 OP,但不确定这样的持久性设计。从同一链接复制:警告:我们个人认为默认允许注册 Func 代表是一种设计味道。 Func 委托的使用使您的设计更难遵循,您的系统更难维护和测试。
【解决方案2】:

查看Simple Injector 的文档,看看下面的link,它详细介绍了 ASP.Net MVC 的依赖注入的无缝实现,其中 DI 框架负责解决运行。我假设您正在使用 MVC 控制器

粘贴相关代码并自定义:

应用程序启动事件

using System.Web.Mvc;
using SimpleInjector;
using SimpleInjector.Integration.Web.Mvc;

public class Global : System.Web.HttpApplication {

    protected void Application_Start(object sender, EventArgs e) {
        // 1. Create a new Simple Injector container
        var container = new Container();

        // 2. Configure the container (register)
        // See below for more configuration examples
        container.Register<IStationController, StationController>(Lifestyle.Transient);

        // 3. Optionally verify the container's configuration.
        container.Verify();

        // 4. Store the container for use by the application
        DependencyResolver.SetResolver(
            new SimpleInjectorDependencyResolver(container));
    }
}

基本控制器

using System;
using System.Web.Mvc;

public class BusinessLogicController : Controller {
    private readonly IStationController stationController;

    public BusinessLogicController(IStationController stationController) {
        this.stationController = stationController;
    }

    // Use the IStationController object in various calls
}

重要(来自文档):

在 MVC 的情况下,第五步(解析实例)是 MVC 框架的职责。对于每个收到的 Web 请求,MVC 框架都会将该请求映射到一个控制器类型,并要求应用程序的 IDependencyResolver 创建该控制器类型的实例。 SimpleInjectorDependencyResolver 的注册(SimpleInjector.Integration.Web.Mvc.dll 的一部分)将确保创建实例的请求被转发到 Simple Injector。 Simple Injector 将创建具有所有嵌套依赖项的控制器。

【讨论】:

  • 嗨 Mrinal Kamboj 这又需要我的 DI 容器引用在我的 bussineslogic 类中,据我在互联网上阅读,这并不好。请让我知道您对此声明的看法。
  • 我建议不要使用静态Factory,尤其是允许解析每种类型的工厂,因为这是Service Locator anti-pattern 的实现(服务位置是与依赖注入相反)。除此之外,将其设为静态会使消费代码难以测试并隐藏依赖关系,并且由于Factory 的程序集将需要对 DI 容器的依赖,因此所有消费程序集也将需要(因为程序集依赖项是可传递的)。跨度>
  • 相反,至少创建一个IFactory&lt;T&gt; 接口,将其注入到消费者的构造函数中。但是您在 Simple Injector 文档中指出警告是正确的。该警告源于此warning about factories in general。然而,鉴于 OP 问题的当前详细程度,很难分析他当前的设计并提出不需要使用工厂的改进。
  • @Steven 感谢我确实意识到我的答案存在设计错误,尤其是服务定位器反模式,但由于缺乏可用信息,我有点困惑,现在我已经修改了文档中与 MVC 注入相关的相关详细信息。假设这将更好地服务于 OP 目的
  • @Christophe,请查看修改后的响应,如果您有 ASP.Net MVC 架构,那么这将更好地达到目的,因为它会自动为 Web 请求注入依赖项
猜你喜欢
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 2017-09-16
  • 1970-01-01
  • 2021-05-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多